From 7347877151f8e2ec19bcb164af38f6108a093592 Mon Sep 17 00:00:00 2001 From: Charadon Date: Jan 05 2023 23:42:48 +0000 Subject: Merge branch v2 --- diff --git a/README.txt b/README.txt index df1a991..c84583a 100644 --- a/README.txt +++ b/README.txt @@ -1,57 +1,63 @@ - _____ _ _ ___ ______ __ __ ______ -/ ____| | | | | |__ \ | ____| \ \ / / | ____| -| (___ | |__| | ) | | |__ \ V / | |__ -\___ \ | __ | / / | __| > < | __| -____) | | | | | / /_ | |____ / . \ | |____ -|_____/ |_| |_| |____| |______| /_/ \_\ |______| -================================================================================ -WHAT IS IT? -------------- -sh2exe is a script that turns POSIX Shell scripts into Windows executables, -therefore allowing you to distribute a Shell Script on Windows without needing -a full Git for Bash or Cygwin environment installed somewhere. - -It currently bundles: -- GNU Coreutils -- GNU Less -- GNU Awk -- GNU Grep -- GNU Nano -- Portable OpenBSD Kornshell -This should give you a roughly complete POSIX Shell experience. - -Currently, the executable comes out at around 6MB, which is pretty small on -modern storage devices! - -USE CASES -------------- -This script can help you, if you need to distribute a script (say, to launch -a game your making *cough*) without needing to make a script *solely* for -Windows. Just turn it into an executable, and presto! You got a script that -runs! - -REQUIREMENTS -------------- -You must run this script in an MSYS2 environment. I might add cygwin support -eventually, but for now, MSYS2 only. However, once the executable has been -generated, you will *not* need MSYS2 installed to run it. - -USAGE -------------- -Simply run sh2exe.sh with the path to the script you want to convert. -Example: ./sh2exe.sh /path/to/script.sh - -You can run it on the included helloworld.sh script to see it in action! - -For more info, run ./sh2exe.sh help - -COPYRIGHT -------------- -The sh2exe.sh script itself is licensed under Apache 2.0, however the source -code template (runsh.c) is licensed under the GNU All-Permissive License, -meaning you can do whatever you want with the generated executable. - -SUPPORT ME -------------- -If you like this program, consider giving some dollary doos at -https://liberapay.com/Charadon + _____ _ _ ___ ______ __ __ ______ +/ ____| | | | | |__ \ | ____| \ \ / / | ____| +| (___ | |__| | ) | | |__ \ V / | |__ +\___ \ | __ | / / | __| > < | __| +____) | | | | | / /_ | |____ / . \ | |____ +|_____/ |_| |_| |____| |______| /_/ \_\ |______| v2 +================================================================================ +WHAT IS IT? +------------- +sh2exe is a script that turns POSIX Shell scripts into Windows executables, +therefore allowing you to distribute a Shell Script on Windows without needing +a full Git for Bash or Cygwin environment installed somewhere. + +It currently bundles: +- GNU Coreutils +- GNU Less +- GNU Awk +- GNU Grep +- GNU Nano +- GNU Bash +- MirBSD Shell +This should give you a roughly complete POSIX Shell experience. + +Currently, the executable comes out at around 6MB, which is pretty small on +modern storage devices! + +USE CASES +------------- +This script can help you, if you need to distribute a script (say, to launch +a game your making *cough*) without needing to make a script *solely* for +Windows. Just turn it into an executable, and presto! You got a script that +runs! + +REQUIREMENTS +------------- +You must run this script in an MSYS2 environment. However, once the executable +has been generated, you will *not* need MSYS2 installed to run it. + +USAGE +------------- +Simply run sh2exe.sh with the path to the script you want to convert. +Example: ./sh2exe.sh -s /path/to/script.sh -w /path/to/workdir + +You can run it on the included helloworld.sh script to see it in action! + +Which shell is used in the runtime depends on the shebang of the script. Here's +a table: + +#!/bin/sh = MirBSD Korn Shell used. +#!/bin/bash = GNU Bash used. + +For more info, run ./sh2exe.sh -h + +COPYRIGHT +------------- +The sh2exe.sh script itself is licensed under Apache 2.0, however the source +code template (runsh.c) is licensed under the GNU All-Permissive License, +meaning you can do whatever you want with the generated executable. + +SUPPORT ME +------------- +If you like this program, consider giving some dollary doos at +https://liberapay.com/Charadon diff --git a/helloworld.sh b/helloworld.sh index f2517ba..38a3519 100644 --- a/helloworld.sh +++ b/helloworld.sh @@ -20,13 +20,13 @@ echo " Value: $HOME" echo "\$USER - Not on windows by default, but again, we provide it for you!" echo " Value: $USER" echo "\$EDITOR - If this isn't set in the user's environment, we make it Nano by default!" -echo " Value: $EDITOR" +echo " Value: $EDITOR" read CHOICE echo "Now i'm going to put a message on your desktop!" read CHOICE echo "How are you $USER? I hope you are having a wonderful day!" > "$HOME/Desktop/greetings.txt" -echo "Now let's open it..." +echo "Now let's open it with \$EDITOR..." read CHOICE $EDITOR "$HOME/Desktop/greetings.txt" echo "Here's all the commands you have available to you:" diff --git a/runsh.c b/runsh.c index 51f656a..3657a8c 100644 --- a/runsh.c +++ b/runsh.c @@ -1,226 +1,240 @@ -/* -* Copyright 2023, Charadon -* -* Copying and distribution of this file, with or without modification, are -* permitted in any medium without royalty, provided the copyright notice and -* this notice are preserved. This file is offered as-is, without any -* warranty. -*/ -#include -#include -#include -#include -#include -#include -#include -#include "7zip.h" -#include "runtime.h" - -void error_at_line(int Line, const char *File, const char *Doing) { - fprintf(stderr, "Program failed at line %d in %s doing or for reason: %s\n", Line, File, Doing); - exit(1); -} - -void dump_file(const char *EmbeddedFile, int EmbeddedFileLength, const char *FilePath, const char *TempDir) { - FILE *FileToDump = NULL; - char FilePathRW[PATH_MAX]; - if(strncpy(FilePathRW, FilePath, PATH_MAX-1) == NULL) { - error_at_line(__LINE__, __FILE__, "strncpy"); - } - char TempDirRW[PATH_MAX]; - if(strncpy(TempDirRW, TempDir, PATH_MAX-1) == NULL) { - error_at_line(__LINE__, __FILE__, "strncpy"); - } - FileToDump = fopen(strncat(TempDirRW, FilePathRW, PATH_MAX-1), "wb"); - if(FileToDump == NULL) { - error_at_line(__LINE__, __FILE__, "fopen"); - } - if(fwrite(EmbeddedFile, 1, EmbeddedFileLength, FileToDump) == 0) { - error_at_line(__LINE__, __FILE__, "fwrite"); - } - if(fclose(FileToDump) != 0) { - error_at_line(__LINE__, __FILE__, "fclose"); - } -} - -char generate_random_character() { - char ValidCharacters[] = { - '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'A', 'B', 'C', 'D', 'E', \ - 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', \ - 'U', 'V', 'W', 'X', 'Y', 'Z' - }; - return(ValidCharacters[rand() % sizeof(ValidCharacters)]); -} - -// Microsoft sucks. -char *mkdtemp(const char *TempPath) { - char *TempPathString = NULL; - TempPathString = malloc(sizeof(char) * PATH_MAX); - if (strncpy(TempPathString, TempPath, PATH_MAX) == NULL) { - error_at_line(__LINE__, __FILE__, "strncpy"); - } - int Length = strlen(TempPathString); - char *cwd = getcwd(NULL, PATH_MAX); - if(cwd == NULL) { - error_at_line(__LINE__, __FILE__, "getcwd"); - } -TryAgain: - for(int i = 0; i <= 6; i++) { - TempPathString[Length-i] = generate_random_character(); - } - // This should fail, as the path shouldn't exist. If it does, reroll. - int Result = chdir(TempPathString); - if(Result == 0) { - if(chdir(cwd) != 0) { - error_at_line(__LINE__, __FILE__, strerror(errno)); - } - goto TryAgain; - } - if(mkdir(TempPathString) != 0) { - fprintf(stderr, "Failed to make temporary directory.\n"); - exit(1); - } - return TempPathString; -} - -int main(int argc, char *argv[]) { - srand(time(NULL)); - // Grab the Windows TEMP Environment Variable. - char *DebugMode = getenv("SH2EXE_DEBUG"); - int Debug = 0; - if(DebugMode != NULL) { - if(strncmp(DebugMode, "1", 2048) == 0) { - Debug = 1; - } - } - char *Temp = getenv("TEMP"); - if(Temp[0] == '\0') { - error_at_line(__LINE__, __FILE__, "getenv"); - } - - // Create a temporary directory. - char *Template = "\\sh2exe.XXXXXX"; - char *TempDir = mkdtemp(strncat(Temp, Template, PATH_MAX)); - if(TempDir == NULL) { - error_at_line(__LINE__, __FILE__, "mkdtemp"); - } - - // Dump embedded files. - dump_file(SevenZip, sizeof(SevenZip), "\\7zip.exe", TempDir); - dump_file(RuntimeArchive, sizeof(RuntimeArchive), "\\runtime.7z", TempDir); - - // Extract runtime with 7zip. - char *CurrentWorkingDirectory = getcwd(NULL, PATH_MAX); - if(CurrentWorkingDirectory == NULL) { - error_at_line(__LINE__, __FILE__, "getcwd"); - } - if (chdir(TempDir) != 0) { - error_at_line(__LINE__, __FILE__, strerror(errno)); - } - int Result = system(".\\7zip.exe x runtime.7z > nul"); - if(Result != 0) { - error_at_line(__LINE__, __FILE__, "Runtime Extraction"); - } - // We don't need runtime.7z nor 7zip.exe anymore. - if(remove(".\\runtime.7z") != 0) { - error_at_line(__LINE__, __FILE__, strerror(errno)); - } - if(remove(".\\7zip.exe") != 0) { - error_at_line(__LINE__, __FILE__, strerror(errno)); - } - - // Set environment variables. - // PATH - char *PathEnv = getenv("PATH"); - if(PathEnv == NULL) { - error_at_line(__LINE__, __FILE__, "getenv"); - } - char NewPathEnv[USHRT_MAX]; - if(strncpy(NewPathEnv, TempDir, PATH_MAX) == NULL) { - error_at_line(__LINE__, __FILE__, "strncpy"); - } - if(strncat(NewPathEnv, "\\usr\\bin;", PATH_MAX) == NULL) { - error_at_line(__LINE__, __FILE__, "strncat"); - } - if(strncat(NewPathEnv, PathEnv, USHRT_MAX-1) == NULL) { - error_at_line(__LINE__, __FILE__, "strncat"); - } - char PathVariable[USHRT_MAX] = "PATH="; - if(putenv(strncat(PathVariable, NewPathEnv, USHRT_MAX-1)) != 0) { - error_at_line(__LINE__, __FILE__, strerror(errno)); - } - if (chdir(CurrentWorkingDirectory) != 0) { - error_at_line(__LINE__, __FILE__, strerror(errno)); - } - // HOME - char *HomeEnv = getenv("HOMEPATH"); - if(HomeEnv == NULL) { - error_at_line(__LINE__, __FILE__, "getenv"); - } - char PosixHomeEnv[USHRT_MAX] = "HOME="; - if(strncat(PosixHomeEnv, HomeEnv, USHRT_MAX-1) == NULL) { - error_at_line(__LINE__, __FILE__, "strncat"); - } - if(putenv(PosixHomeEnv) != 0) { - error_at_line(__LINE__, __FILE__, strerror(errno)); - } - // USER - char *UserEnv = getenv("USERNAME"); - if(UserEnv == NULL) { - error_at_line(__LINE__, __FILE__, "getenv"); - } - char PosixUserEnv[USHRT_MAX] = "USER="; - if(strncat(PosixUserEnv, UserEnv, USHRT_MAX-1) == NULL) { - error_at_line(__LINE__, __FILE__, "strncat"); - } - if(putenv(PosixUserEnv) != 0) { - error_at_line(__LINE__, __FILE__, strerror(errno)); - } - // USER - char *EditorEnv = getenv("EDITOR"); - if(EditorEnv == NULL) { - if(putenv("EDITOR=/usr/bin/nano") != 0) { - error_at_line(__LINE__, __FILE__, "Failed to set EDITOR"); - } - } - // Format command string and then run it. - char Command[PATH_MAX]; - char CommandArgs[USHRT_MAX]; - for(int i = 1; i < argc; i++) { - if (strncat(CommandArgs, " ", USHRT_MAX-1) == NULL) { - error_at_line(__LINE__, __FILE__, "strncat"); - } - if (strncat(CommandArgs, argv[i], USHRT_MAX-1) == NULL) { - error_at_line(__LINE__, __FILE__, "strncat"); - } - } - char ScriptName[USHRT_MAX] = "sh"; - if(Debug == 0) { - if(strncpy(ScriptName, "INSERT_SCRIPT_NAME_HERE", USHRT_MAX) == NULL) { - error_at_line(__LINE__, __FILE__, "strncpy"); - } - } - else { - if(strncpy(ScriptName, "sh", USHRT_MAX) == NULL) { - error_at_line(__LINE__, __FILE__, "strncpy"); - } - } - if(snprintf(Command, PATH_MAX-1, "%s\\usr\\bin\\sh -c \"/usr/bin/%s %s\"", TempDir, ScriptName, CommandArgs) < 0) { - error_at_line(__LINE__, __FILE__, "snprintf"); - } - Result = system(Command); - - // Cleanup Temporary Directory. - char DeleteTempDirPath[PATH_MAX]; - if(strncpy(DeleteTempDirPath, TempDir, PATH_MAX-1) == NULL) { - error_at_line(__LINE__, __FILE__, "strncpy"); - } - char RmDirCmd[PATH_MAX] = "rmdir /Q /S "; - if(strncat(RmDirCmd, DeleteTempDirPath, PATH_MAX-1) == NULL) { - error_at_line(__LINE__, __FILE__, "strncat"); - } - if (system(RmDirCmd) != 0) { - error_at_line(__LINE__, __FILE__, "Couldn't delete temporary directory of runtime."); - } - return(Result); -} +/* +* Copyright 2023, Charadon +* +* Copying and distribution of this file, with or without modification, are +* permitted in any medium without royalty, provided the copyright notice and +* this notice are preserved. This file is offered as-is, without any +* warranty. +*/ +#include +#include +#include +#include +#include +#include +#include +#include "7zip.h" +#include "runtime.h" + +void error_at_line(int Line, const char *File, const char *Doing) { + fprintf(stderr, "Program failed at line %d in %s doing or for reason: %s\n", Line, File, Doing); + exit(1); +} + +void dump_file(const char *EmbeddedFile, int EmbeddedFileLength, const char *FilePath, const char *TempDir) { + FILE *FileToDump = NULL; + char FilePathRW[PATH_MAX]; + if(strncpy(FilePathRW, FilePath, PATH_MAX-1) == NULL) { + error_at_line(__LINE__, __FILE__, "strncpy"); + } + char TempDirRW[PATH_MAX]; + if(strncpy(TempDirRW, TempDir, PATH_MAX-1) == NULL) { + error_at_line(__LINE__, __FILE__, "strncpy"); + } + FileToDump = fopen(strncat(TempDirRW, FilePathRW, PATH_MAX-1), "wb"); + if(FileToDump == NULL) { + error_at_line(__LINE__, __FILE__, "fopen"); + } + if(fwrite(EmbeddedFile, 1, EmbeddedFileLength, FileToDump) == 0) { + error_at_line(__LINE__, __FILE__, "fwrite"); + } + if(fclose(FileToDump) != 0) { + error_at_line(__LINE__, __FILE__, "fclose"); + } +} + +char generate_random_character() { + char ValidCharacters[] = { + '1', '2', '3', '4', '5', '6', '7', '8', '9', '0', 'A', 'B', 'C', 'D', 'E', \ + 'F', 'G', 'H', 'I', 'J', 'K', 'L', 'M', 'N', 'O', 'P', 'Q', 'R', 'S', 'T', \ + 'U', 'V', 'W', 'X', 'Y', 'Z' + }; + return(ValidCharacters[rand() % sizeof(ValidCharacters)]); +} + +// Microsoft sucks. +char *mkdtemp(const char *TempPath) { + char *TempPathString = NULL; + TempPathString = malloc(sizeof(char) * PATH_MAX); + if (strncpy(TempPathString, TempPath, PATH_MAX) == NULL) { + error_at_line(__LINE__, __FILE__, "strncpy"); + } + int Length = strlen(TempPathString); + char *cwd = getcwd(NULL, PATH_MAX); + if(cwd == NULL) { + error_at_line(__LINE__, __FILE__, "getcwd"); + } +TryAgain: + for(int i = 0; i <= 6; i++) { + TempPathString[Length-i] = generate_random_character(); + } + // This should fail, as the path shouldn't exist. If it does, reroll. + int Result = chdir(TempPathString); + if(Result == 0) { + if(chdir(cwd) != 0) { + error_at_line(__LINE__, __FILE__, strerror(errno)); + } + goto TryAgain; + } + if(mkdir(TempPathString) != 0) { + fprintf(stderr, "Failed to make temporary directory.\n"); + exit(1); + } + return TempPathString; +} + +int main(int argc, char *argv[]) { + srand(time(NULL)); + // Grab the Windows TEMP Environment Variable. + char *DebugMode = getenv("SH2EXE_DEBUG"); + int Debug = 0; + if(DebugMode != NULL) { + if(strncmp(DebugMode, "1", 2048) == 0) { + Debug = 1; + } + } + char *Temp = getenv("TEMP"); + if(Temp[0] == '\0') { + error_at_line(__LINE__, __FILE__, "getenv"); + } + + // Create a temporary directory. + char *Template = "\\sh2exe.XXXXXX"; + char *TempDir = mkdtemp(strncat(Temp, Template, PATH_MAX)); + if(TempDir == NULL) { + error_at_line(__LINE__, __FILE__, "mkdtemp"); + } + + // Dump embedded files. + dump_file(SevenZip, sizeof(SevenZip), "\\7zip.exe", TempDir); + dump_file(RuntimeArchive, sizeof(RuntimeArchive), "\\runtime.7z", TempDir); + + // Extract runtime with 7zip. + char *CurrentWorkingDirectory = getcwd(NULL, PATH_MAX); + if(CurrentWorkingDirectory == NULL) { + error_at_line(__LINE__, __FILE__, "getcwd"); + } + if (chdir(TempDir) != 0) { + error_at_line(__LINE__, __FILE__, strerror(errno)); + } + int Result = system(".\\7zip.exe x runtime.7z > nul"); + if(Result != 0) { + error_at_line(__LINE__, __FILE__, "Runtime Extraction"); + } + // We don't need runtime.7z nor 7zip.exe anymore. + if(remove(".\\runtime.7z") != 0) { + error_at_line(__LINE__, __FILE__, strerror(errno)); + } + if(remove(".\\7zip.exe") != 0) { + error_at_line(__LINE__, __FILE__, strerror(errno)); + } + + // Fix issue where bash will complain about missing /tmp + char RuntimeTmpDir[PATH_MAX]; + if(strncpy(RuntimeTmpDir, TempDir, PATH_MAX-1) == NULL) { + error_at_line(__LINE__, __FILE__, "strncpy"); + } + if(strncat(RuntimeTmpDir, "\\tmp", PATH_MAX-1) == NULL) { + error_at_line(__LINE__, __FILE__, "strncat"); + } + if(mkdir(RuntimeTmpDir) != 0) { + if(errno != EEXIST) { + error_at_line(__LINE__, __FILE__, strerror(errno)); + } + } + + // Set environment variables. + // PATH + char *PathEnv = getenv("PATH"); + if(PathEnv == NULL) { + error_at_line(__LINE__, __FILE__, "getenv"); + } + char NewPathEnv[USHRT_MAX]; + if(strncpy(NewPathEnv, TempDir, PATH_MAX) == NULL) { + error_at_line(__LINE__, __FILE__, "strncpy"); + } + if(strncat(NewPathEnv, "\\usr\\bin;", PATH_MAX) == NULL) { + error_at_line(__LINE__, __FILE__, "strncat"); + } + if(strncat(NewPathEnv, PathEnv, USHRT_MAX-1) == NULL) { + error_at_line(__LINE__, __FILE__, "strncat"); + } + char PathVariable[USHRT_MAX] = "PATH="; + if(putenv(strncat(PathVariable, NewPathEnv, USHRT_MAX-1)) != 0) { + error_at_line(__LINE__, __FILE__, strerror(errno)); + } + if (chdir(CurrentWorkingDirectory) != 0) { + error_at_line(__LINE__, __FILE__, strerror(errno)); + } + // HOME + char *HomeEnv = getenv("HOMEPATH"); + if(HomeEnv == NULL) { + error_at_line(__LINE__, __FILE__, "getenv"); + } + char PosixHomeEnv[USHRT_MAX] = "HOME="; + if(strncat(PosixHomeEnv, HomeEnv, USHRT_MAX-1) == NULL) { + error_at_line(__LINE__, __FILE__, "strncat"); + } + if(putenv(PosixHomeEnv) != 0) { + error_at_line(__LINE__, __FILE__, strerror(errno)); + } + // USER + char *UserEnv = getenv("USERNAME"); + if(UserEnv == NULL) { + error_at_line(__LINE__, __FILE__, "getenv"); + } + char PosixUserEnv[USHRT_MAX] = "USER="; + if(strncat(PosixUserEnv, UserEnv, USHRT_MAX-1) == NULL) { + error_at_line(__LINE__, __FILE__, "strncat"); + } + if(putenv(PosixUserEnv) != 0) { + error_at_line(__LINE__, __FILE__, strerror(errno)); + } + // USER + char *EditorEnv = getenv("EDITOR"); + if(EditorEnv == NULL) { + if(putenv("EDITOR=/usr/bin/nano") != 0) { + error_at_line(__LINE__, __FILE__, "Failed to set EDITOR"); + } + } + // Format command string and then run it. + char Command[PATH_MAX]; + char CommandArgs[USHRT_MAX]; + for(int i = 1; i < argc; i++) { + if (strncat(CommandArgs, " ", USHRT_MAX-1) == NULL) { + error_at_line(__LINE__, __FILE__, "strncat"); + } + if (strncat(CommandArgs, argv[i], USHRT_MAX-1) == NULL) { + error_at_line(__LINE__, __FILE__, "strncat"); + } + } + char ScriptName[USHRT_MAX] = "sh"; + if(Debug == 0) { + if(strncpy(ScriptName, "INSERT_SCRIPT_NAME_HERE", USHRT_MAX) == NULL) { + error_at_line(__LINE__, __FILE__, "strncpy"); + } + } + else { + if(strncpy(ScriptName, "sh", USHRT_MAX) == NULL) { + error_at_line(__LINE__, __FILE__, "strncpy"); + } + } + if(snprintf(Command, PATH_MAX-1, "%s\\usr\\bin\\sh -c \"/usr/bin/%s %s\"", TempDir, ScriptName, CommandArgs) < 0) { + error_at_line(__LINE__, __FILE__, "snprintf"); + } + Result = system(Command); + + // Cleanup Temporary Directory. + char DeleteTempDirPath[PATH_MAX]; + if(strncpy(DeleteTempDirPath, TempDir, PATH_MAX-1) == NULL) { + error_at_line(__LINE__, __FILE__, "strncpy"); + } + char RmDirCmd[PATH_MAX] = "rmdir /Q /S "; + if(strncat(RmDirCmd, DeleteTempDirPath, PATH_MAX-1) == NULL) { + error_at_line(__LINE__, __FILE__, "strncat"); + } + if (system(RmDirCmd) != 0) { + error_at_line(__LINE__, __FILE__, "Couldn't delete temporary directory of runtime."); + } + return(Result); +} diff --git a/sh2exe.sh b/sh2exe.sh index a2f2fae..0a25f04 100644 --- a/sh2exe.sh +++ b/sh2exe.sh @@ -1,396 +1,232 @@ #!/bin/sh -# Copyright 2023 Charadon -# -# Licensed under the Apache License, Version 2.0 (the "License"); -# you may not use this file except in compliance with the License. -# You may obtain a copy of the License at -# -# http://www.apache.org/licenses/LICENSE-2.0 -# -# Unless required by applicable law or agreed to in writing, software -# distributed under the License is distributed on an "AS IS" BASIS, -# WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. -# See the License for the specific language governing permissions and -# limitations under the License. - -if [ ! "$MSYSTEM" = "MSYS" ]; -then - echo "You must run MSYS2 with the MSYS runtime." - exit 1 -fi help_screen() { - echo "Usage: $0 [verb/file]" - echo "Turns a POSIX shell script into a Windows Executable." - printf "\n" - echo "Valid Verbs:" - printf "help Displays this screen.\n" - printf "clean Cleans the workspace.\n" - printf " Tells script which script to turn into executable.\n\n" - echo "Exit status:" - echo " 0 = Success." - echo " 1 = Script failed before build part started." - echo " 2 = Checksum mismatch of downloaded files." - echo " 5 = Failed to change directory at some point in the script." - printf "\n" - echo "For more information visit sh2exe's website at: https://sh2exe.iotib.net" - echo "or the git repo at: https://pagure.io/DaLazySlackers/sh2exe" - exit "$1" + echo "Usage: $0 [-s FILE] [-w DIR]" + echo "Turns a POSIX shell script into a Windows Executable." + printf "\n" + echo "Options:" + echo "-s=FILE Script to convert to executable." + echo "-w=DIR Directory to use as a work directory." + echo "-h Shows this screen." + echo "-c Cleans workspace." + echo "-l Shows license of this script." + printf "\n" + echo "Example:" + echo "# Converts helloworld.sh to an executable while using a different workspace by default." + echo "$0 -s helloworld.sh -w /tmp/sh2exe_work" + printf "\n" + echo "Exit status:" + echo "0 = Success." + echo "1 = Initialization failure. (Before runtime was being built.)" + echo "5 = Failed to change directory while runtime was building." + printf "\n" + echo "Website : sh2exe.iotib.net" + echo "Git : https://pagure.io/dalazyslackers/sh2exe" + return 0 } -SEVEN_ZIP="7zr.exe" - -if [ -z "$1" ]; -then - help_screen 1 -elif [ "$1" = "help" ]; -then - help_screen 0 -fi - -set -eu - -WORKDIR="$PWD/work" -rm -rf "$WORKDIR" -mkdir -p "$WORKDIR" -rm output.log > /dev/null 2>&1 || true -STARTING_DIR="$PWD" - -COREUTILS_SHA512SUM="a6ee2c549140b189e8c1b35e119d4289ec27244ec0ed9da0ac55202f365a7e33778b1dc7c4e64d1669599ff81a8297fe4f5adbcc8a3a2f75c919a43cd4b9bdfa *coreutils-9.1.tar.xz" -COREUTILS_VERSION="9.1" -OKSH_SHA512SUM="1b412ebbae58bdb1ccf9b9e0b64bab233cd5e184a02bc67db9b4ba82fce0749b8166dd688c24fd890d22a111d7f6ba859c47e3175f28675fca9c6def8c416a08 *oksh-7.2.tar.gz" -OKSH_VERSION="7.2" -NANO_SHA512SUM="b1eb5c06460b1407e3fc959070d2eaaba29d236ef099654fcbf9ba7cf7b22f64ebc9e6f232d471601c35f0e1c78d8c7aad9269e2afed25c0899572b97d44a95c *nano-7.1.tar.gz" -NANO_VERSION="7.1" -LESS_SHA512SUM="7945b7f88921832ebb1b45fba8cbb449ee0133342796b654a52c146dfff3d84db18724ee84e53349eeea6017a0ebe2d8eb5366210275981dde7bb7190118fa66 *less-608.tar.gz" -LESS_VERSION="608" -GAWK_SHA512SUM="1d2aaf90bd074379e13dce65b53e80d449c383ff1f2862bb294644b2053383c19172d3b68bfd9caad82ccd11bea1baf627994280e845da9fdd0e773a000cfe40 *gawk-5.2.1.tar.gz" -GAWK_VERSION="5.2.1" -GREP_SHA512SUM="69cf162886770c4ff86f869f78d8a7e291480f576a92f2afa87c42290c7d34c4ca5cf52e47d10c8e310eadd605d209a2701dc714b651d122be9eca1e73130409 *grep-3.8.tar.gz" -GREP_VERSION="3.8" +show_license() { + echo " + Copyright 2023 Charadon -# Speed up compilation times. -# Doesn't work: https://github.com/msys2/MSYS2-packages/issues/2492 -#CC="ccache gcc" -#export CC -#CXX="ccache g++" -#export CXX + Licensed under the Apache License, Version 2.0 (the \"License\"); + you may not use this file except in compliance with the License. + You may obtain a copy of the License at + http://www.apache.org/licenses/LICENSE-2.0 + Unless required by applicable law or agreed to in writing, software + distributed under the License is distributed on an \"AS IS\" BASIS, + WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + See the License for the specific language governing permissions and + limitations under the License. + " + return 0 +} -if [ "$1" = "clean" ]; -then - echo "Cleaning... " - rm -rvf "$WORKDIR" || true - rm -rvf "coreutils-$COREUTILS_VERSION/" || true - rm -rvf "oksh-$OKSH_VERSION/" || true - rm -rvf "nano-$NANO_VERSION/" || true - rm -rvf "gawk-$GAWK_VERSION/" || true - rm -rvf "grep-$GREP_VERSION/" || true - rm -rvf "less-$LESS_VERSION/" || true - rm -f ./*.tar.?z || true - rm -f ./*.7z || true - rm -f 7zip.h || true - rm -f runtime.h || true - rm -f 7zr.exe || true - rm -f *.stackdump - cd bin2c - make clean - cd .. - exit 0 -else - SHEBANG="$(head -1 "$1")" - if [ ! "$SHEBANG" = "#!/bin/sh" ]; - then - echo "Shebang needs to be #!/bin/sh." - exit 1 - fi - SCRIPT_NAME="$1" -fi +clean() { + set -u + set +e + rm -rvf "$WORKDIR" + rm -fv "7zr.exe" + rm -fv output.log + make -C bin2c clean + return 0 +} -echo "If your terminal closes, that means MSYS had an update that required a runtime restart." -echo "If the script ends, but there's no checkmark after the task, check the output.log for errors." -printf "Updating system... " -yes | pacman -Syu >> output.log 2>&1 -printf "✓\n" -# Check for missing dependencies. -printf "\rInstalling dependencies... |" -MISSING_DEPENDENCIES="" -if ! pacman -Qi ncurses-devel > /dev/null 2>&1; -then - MISSING_DEPENDENCIES="$MISSING_DEPENDENCIES ncurses-devel" -fi -printf "\rInstalling dependencies... /" -if ! pacman -Qi gcc > /dev/null 2>&1; -then - MISSING_DEPENDENCIES="$MISSING_DEPENDENCIES gcc" -fi -printf "\rInstalling dependencies... -" -if ! pacman -Qi texinfo > /dev/null 2>&1; -then - MISSING_DEPENDENCIES="$MISSING_DEPENDENCIES texinfo" -fi -printf "\rInstalling dependencies... \\" -if ! pacman -Qi make > /dev/null 2>&1; -then - MISSING_DEPENDENCIES="$MISSING_DEPENDENCIES make" -fi -printf "\rInstalling dependencies... |" -if ! pacman -Qi mingw-w64-x86_64-gcc > /dev/null 2>&1; -then - MISSING_DEPENDENCIES="$MISSING_DEPENDENCIES mingw-w64-x86_64-gcc" -fi -printf "\rInstalling dependencies... /" -if ! pacman -Qi grep > /dev/null 2>&1; -then - MISSING_DEPENDENCIES="$MISSING_DEPENDENCIES grep" -fi -printf "\rInstalling dependencies... -" -if ! pacman -Qi coreutils > /dev/null 2>&1; +if [ -z "$1" ]; then - MISSING_DEPENDENCIES="$MISSING_DEPENDENCIES coreutils" + help_screen + exit 1 fi -printf "\rInstalling dependencies... \\" -if ! pacman -Qi curl > /dev/null 2>&1; +CLEAN="false" +while getopts ":s:w:chl" opt; +do + case "${opt}" in + s) + SCRIPT="${OPTARG}" + if [ ! -f "$SCRIPT" ]; + then + echo "File doesn't exist." + exit 1 + fi + SHEBANG="$(head -1 "$SCRIPT")" + if [ ! "$SHEBANG" = "#!/bin/sh" ] && [ ! "$SHEBANG" = "#!/bin/bash" ]; + then + echo "File has invalid shebang. Aborting." + exit 1 + fi + ;; + w) + WORKDIR="${OPTARG}" + ;; + h) + help_screen + exit 0 + ;; + c) + CLEAN="true" + exit 0 + ;; + l) + show_license + exit 0 + ;; + *) + help_screen + exit 1 + ;; + esac +done + +if [ "$CLEAN" = "true" ]; then - MISSING_DEPENDENCIES="$MISSING_DEPENDENCIES curl" + clean fi -# Install missing dependencies -if [ ! "$MISSING_DEPENDENCIES" = "" ]; + +if [ -z "$SCRIPT" ]; then - yes | pacman -S $MISSING_DEPENDENCIES >> output.log 2>&1 + help_screen + exit 1 fi -printf "\rInstalling dependencies... ✓\n" - -download_7zip() { - curl "https://7-zip.org/a/7zr.exe" -O -} -build_coreutils() { - curl "https://ftp.gnu.org/pub/gnu/coreutils/coreutils-$COREUTILS_VERSION.tar.xz" -O - if [ ! "$(sha512sum coreutils-"$COREUTILS_VERSION".tar.xz)" = "$COREUTILS_SHA512SUM" ]; - then - echo "Checksum mismatch for coreutils." - exit 2 - fi - if [ -f "coreutils-$COREUTILS_VERSION/DONE" ]; - then - cd "coreutils-$COREUTILS_VERSION" || exit 5 - make DESTDIR="$WORKDIR" install-exec - cd "$STARTING_DIR" || exit 5 - return 0 - fi - tar -xvf "coreutils-$COREUTILS_VERSION".tar.xz - cd "coreutils-$COREUTILS_VERSION" || exit 5 - make clean || true - ./configure --prefix=/usr - make DESTDIR="$WORKDIR" -j "$(nproc)" install-exec - touch DONE - cd "$STARTING_DIR" || exit 5 - return 0 -} - -build_nano() { - curl "https://ftp.gnu.org/gnu/nano/nano-$NANO_VERSION.tar.gz" -O - if [ ! "$(sha512sum nano-$NANO_VERSION.tar.gz)" = "$NANO_SHA512SUM" ]; - then - echo "Checksum mismatch for nano." - exit 2 - fi - if [ -f "nano-$NANO_VERSION/DONE" ]; - then - cd "nano-$NANO_VERSION" || exit 5 - make DESTDIR="$WORKDIR" install-exec - cd "$STARTING_DIR" || exit 5 - return 0 - fi - tar -xvf "nano-$NANO_VERSION.tar.gz" - cd "nano-$NANO_VERSION" || exit 5 - make clean || true - ./configure --prefix=/usr --enable-tiny - make -j "$(nproc)" - make DESTDIR="$WORKDIR" -j "$(nproc)" install - touch DONE - cd "$STARTING_DIR" || exit 5 - return 0 -} - -build_oksh() { - curl "https://github.com/ibara/oksh/releases/download/oksh-$OKSH_VERSION/oksh-$OKSH_VERSION.tar.gz" -OL - if [ ! "$(sha512sum oksh-$OKSH_VERSION.tar.gz)" = "$OKSH_SHA512SUM" ]; - then - echo "Checksum mismatch for OKSH." - exit 2 - fi - if [ -f oksh-$OKSH_VERSION/DONE ]; - then - cd "oksh-$OKSH_VERSION" || exit 5 - make DESTDIR="$WORKDIR" install - cd "$STARTING_DIR" || exit 5 - return 0 - fi - tar -xvf "oksh-$OKSH_VERSION.tar.gz" - cd "oksh-$OKSH_VERSION" || exit 5 - make clean || true - ./configure --enable-curses --enable-sh --prefix=/usr - make -j "$(nproc)" - make DESTDIR="$WORKDIR" install - touch DONE - cd "$STARTING_DIR" || exit 5 - return 0 -} +set -eu -build_less() { - curl "https://ftp.gnu.org/gnu/less/less-$LESS_VERSION.tar.gz" -O - if [ ! "$(sha512sum less-$LESS_VERSION.tar.gz)" = "$LESS_SHA512SUM" ]; - then - echo "Checksum mismatch for Less" - exit 2 - fi - if [ -f less-$LESS_VERSION/DONE ]; - then - cd "less-$LESS_VERSION" || exit 5 - make DESTDIR="$WORKDIR" install - cd "$STARTING_DIR" || exit 5 - return 0 - fi - tar -xvf "less-$LESS_VERSION.tar.gz" - cd "less-$LESS_VERSION" || exit 5 - make clean || true - ./configure --prefix=/usr - make -j "$(nproc)" - make DESTDIR="$WORKDIR" install - touch DONE - cd "$STARTING_DIR" || exit 5 - return 0 -} +if [ ! "$MSYSTEM" = "MSYS" ]; +then + echo "You must run MSYS2 with the MSYS runtime." + exit 1 +fi -build_gawk() { - curl "https://ftp.gnu.org/gnu/gawk/gawk-$GAWK_VERSION.tar.gz" -O - if [ ! "$(sha512sum gawk-$GAWK_VERSION.tar.gz)" = "$GAWK_SHA512SUM" ]; - then - echo "Checksum mismatch for Gawk" - exit 2 - fi - if [ -f gawk-$GAWK_VERSION/DONE ]; - then - cd "gawk-$GAWK_VERSION" || exit 5 - make DESTDIR="$WORKDIR" install - cd "$STARTING_DIR" || exit 5 - return 0 - fi - tar -xvf "gawk-$GAWK_VERSION.tar.gz" - cd "gawk-$GAWK_VERSION" || exit 5 - make clean || true - ./configure --prefix=/usr - make -j "$(nproc)" - make DESTDIR="$WORKDIR" install - touch DONE - cd "$STARTING_DIR" || exit 5 - return 0 +wait_for_finish() { + printf "|" + while kill -0 "$1" > /dev/null 2>&1; + do + printf "\b|" + sleep 0.1 + printf "\b/" + sleep 0.1 + printf "\b-" + sleep 0.1 + printf "\b\\" + sleep 0.1 + done + printf "\b" + wait "$1" + return "$?" } -build_grep() { - curl "https://ftp.gnu.org/gnu/grep/grep-$GREP_VERSION.tar.gz" -O - if [ ! "$(sha512sum grep-$GREP_VERSION.tar.gz)" = "$GREP_SHA512SUM" ]; - then - echo "Checksum mismatch for Grep" - exit 2 - fi - if [ -f grep-$GREP_VERSION/DONE ]; - then - cd "grep-$GREP_VERSION" || exit 5 - make DESTDIR="$WORKDIR" install - cd "$STARTING_DIR" || exit 5 - return 0 - fi - tar -xvf "grep-$GREP_VERSION.tar.gz" - cd "grep-$GREP_VERSION" || exit 5 - make clean || true - ./configure --prefix=/usr - make -j "$(nproc)" - make DESTDIR="$WORKDIR" install - touch DONE - cd "$STARTING_DIR" || exit 5 - return 0 -} +# Create work directory. +WORKDIR="${WORKDIR:-$PWD/work}" +rm -rf "$WORKDIR" +mkdir -p "$WORKDIR/var/lib/pacman" -build_bin2c() { - cd bin2c || exit 5 - make - cd "$STARTING_DIR" || exit 5 -} +OUTPUT_LOG="${OUTPUT_LOG:-output.log}" +rm -f "$OUTPUT_LOG" -cleanup_work() { - # Remove man pages and headers. - rm -rf "${WORKDIR:?}/usr/share/man" - rm -rf "${WORKDIR:?}/usr/share/info" - rm -rf "${WORKDIR:?}/usr/include" - # Strip binaries - for i in "$WORKDIR/usr/bin"/*.exe; - do - strip --strip-unneeded "$i" - done - # Add DLLs from MSYS so executables can run. - cp "/usr/bin/msys-ncursesw6.dll" "$WORKDIR/usr/bin" - cp "/usr/bin/msys-2.0.dll" "$WORKDIR/usr/bin" - cp "/usr/bin/msys-iconv-2.dll" "$WORKDIR/usr/bin" - cp "/usr/bin/msys-intl-8.dll" "$WORKDIR/usr/bin" - cp "/usr/bin/msys-pcre-1.dll" "$WORKDIR/usr/bin" - cp "/usr/bin/msys-gcc_s-seh-1.dll" "$WORKDIR/usr/bin" - # Strip DLLs to save space. - for i in "$WORKDIR/usr/bin"/*.dll; - do - strip --strip-unneeded "$i" - done - cp "$SCRIPT_NAME" "$WORKDIR/usr/bin" - mkdir -p "$WORKDIR/usr/share/terminfo/63/" - cp "/usr/share/terminfo/63/cygwin" "$WORKDIR/usr/share/terminfo/63/" - # Time to 7zip it up. - ./$SEVEN_ZIP a -mx1 runtime.7z "$WORKDIR/*" -} +# Update system +printf "Updating system... " +yes | pacman -Syu >> "$OUTPUT_LOG" 2>&1 & +wait_for_finish "$!" +printf "\rUpdating system... ✓\n" + +# Install dependencies +printf "Installing dependencies... " +yes | pacman -S curl make gcc mingw-w64-x86_64-gcc >> "$OUTPUT_LOG" 2>&1 & +wait_for_finish "$!" +printf "\rInstalling dependencies... ✓\n" -build_runner() { - cp runsh.c "$WORKDIR/$SCRIPT_NAME.c" - bin2c/bin2c.exe "$SEVEN_ZIP" "$WORKDIR/7zip.h" SevenZip - bin2c/bin2c.exe "runtime.7z" "$WORKDIR/runtime.h" RuntimeArchive - sed -i "s|INSERT_SCRIPT_NAME_HERE|$(basename "$SCRIPT_NAME")|g" "$WORKDIR/$SCRIPT_NAME.c" - MSYSTEM="MINGW64" bash -l -c "gcc -I$WORKDIR -Wall -O1 \"$WORKDIR/$(basename $SCRIPT_NAME).c\" -o \"$WORKDIR/$(basename $SCRIPT_NAME).exe\"" -} +# Download 7-zip printf "Downloading 7-zip... " -download_7zip >> output.log 2>&1 -printf "✓\n" -printf "Compiling and installing GNU Coreutils... " -build_coreutils >> output.log 2>&1 -printf "✓\n" -printf "Installing some of MSYS2's Coreutils to workaround bugs... " -install -Dm755 "/usr/bin/ls" "$WORKDIR/usr/bin/ls" -install -Dm755 "/usr/bin/du" "$WORKDIR/usr/bin/du" -printf "✓\n" -printf "Compiling and installing GNU Awk... " -build_gawk >> output.log 2>&1 -printf "✓\n" -# Compiling grep and installing it results in segfault. We'll use MSYS2's for now. -printf "Installing GNU Grep... " -install -Dm755 "/usr/bin/grep" "$WORKDIR/usr/bin/grep" -printf "✓\n" -printf "Compiling and installing GNU Nano Text Editor... " -build_nano >> output.log 2>&1 -printf "✓\n" -printf "Compiling and installing GNU Less... " -build_less >> output.log 2>&1 -printf "✓\n" -printf "Compiling and installing Portable OpenBSD ksh... " -build_oksh >> output.log 2>&1 -printf "✓\n" -printf "Compiling bin2c... " -build_bin2c >> output.log 2>&1 -printf "✓\n" -printf "Creating runtime archive... " -cleanup_work >> output.log 2>&1 -printf "✓\n" +curl "https://7-zip.org/a/7zr.exe" -O >> "$OUTPUT_LOG" 2>&1 & +wait_for_finish "$!" +printf "\rDownloading 7-zip... ✓\n" + +# Compile bin2c to embed runtime and 7zip into source file. +printf "Building bin2c... " +cd bin2c || exit 5 +make clean >> "$OUTPUT_LOG" 2>&1 +make -j "$(nproc)" >> "$OUTPUT_LOG" 2>&1 +cd .. || exit 5 +printf "\rBuilding bin2c... ✓\n" + +# Create runtime environment. +printf "Creating runtime environment... " +yes | pacman --root "$WORKDIR" -Sy >> "$OUTPUT_LOG" 2>&1 & +wait_for_finish "$!" +yes | pacman --root "$WORKDIR" -S \ + coreutils \ + msys2-runtime \ + grep \ + gawk \ + less \ + sed \ + mksh \ + nano >> "$OUTPUT_LOG" 2>&1 & +wait_for_finish "$!" +cp "$SCRIPT" "$WORKDIR/usr/bin/" +printf "\rCreating runtime environment... ✓\n" + +# Remove useless files from runtime environment. +printf "Pruning useless stuff from runtime... " +rm -rf "${WORKDIR:?}/var" +rm -rf "${WORKDIR:?}/usr/share/info" +rm -rf "${WORKDIR:?}/usr/share/man" +rm -rf "${WORKDIR:?}/usr/share/doc" +rm -rf "${WORKDIR:?}/usr/share/licenses" +rm -rf "${WORKDIR:?}/usr/share/locale" +rm -rf "${WORKDIR:?}/usr/include" +for i in $WORKDIR/usr/bin/*.exe; +do + strip --strip-unneeded "$i" +done +for i in $WORKDIR/usr/bin/*.dll; +do + strip --strip-unneeded "$i" +done +printf "\rPruning useless stuff from runtime... ✓\n" + +# Compress the runtime down with 7zip +printf "Create runtime archive... " +# If shebang is the POSIX Shell (/bin/sh), use MirBSD Kornshell instead. +if [ "$SHEBANG" = "#!/bin/sh" ]; +then + rm "$WORKDIR/usr/bin/sh" # Remove bash + cp "$WORKDIR/usr/bin/mksh" "$WORKDIR/usr/bin/sh" # Put mksh in it's place. +fi +./7zr.exe a "$WORKDIR"/runtime.7z "$WORKDIR/*" >> "$OUTPUT_LOG" 2>&1 & +wait_for_finish "$!" +printf "\rCreate runtime archive... ✓\n" + printf "Compiling executable... " -build_runner >> output.log 2>&1 -printf "✓\n" -echo "If all went well, your new executable should be in $WORKDIR/$(basename $SCRIPT_NAME).exe" -exit 0 +cp runsh.c "$WORKDIR/$(basename $SCRIPT).c" +sed -i "s/INSERT_SCRIPT_NAME_HERE/$(basename $SCRIPT)/g" "$WORKDIR/$(basename $SCRIPT).c" +bin2c/bin2c.exe "7zr.exe" "$WORKDIR/7zip.h" SevenZip >> "$OUTPUT_LOG" 2>&1 & +wait_for_finish "$!" +bin2c/bin2c.exe "$WORKDIR/runtime.7z" "$WORKDIR/runtime.h" RuntimeArchive >> "$OUTPUT_LOG" 2>&1 & +wait_for_finish "$!" +MSYSTEM="MINGW64" bash -l -c "gcc -I$WORKDIR -Wall -O1 \"$WORKDIR/$(basename $SCRIPT).c\" -o \"$WORKDIR/$(basename $SCRIPT).exe\"" & +wait_for_finish "$!" +printf "\rCompiling executable... ✓\n" +echo "If everything went well, this script should've made an executable at:" +echo "$WORKDIR/$(basename $SCRIPT).exe"