diff --git a/.github/workflows/build.yml b/.github/workflows/build.yml index 7bc10394da4..8cd4acdc7be 100644 --- a/.github/workflows/build.yml +++ b/.github/workflows/build.yml @@ -64,18 +64,23 @@ jobs: - uses: actions/checkout@v4 with: fetch-depth: 1 - - name: Set up JDK 8 + - name: Set up JDK 17 uses: actions/setup-java@v3 with: - java-version: '8' + java-version: '17' distribution: 'temurin' + cache: maven + - name: Set up Maven + uses: stCarolas/setup-maven@v4.5 + with: + maven-version: 3.9.2 - name: Visual Studio shell uses: egor-tensin/vs-shell@v2 with: arch: x64 - name: Build working-directory: features/org.eclipse.equinox.executable.feature/library/win32 - run: nmake -f make_win64.mak + run: nmake -f make_win64.mak test shell: pwsh - name: Upload artifacts uses: actions/upload-artifact@v3 @@ -86,6 +91,49 @@ jobs: features/org.eclipse.equinox.executable.feature/library/win32/eclipse*.exe features/org.eclipse.equinox.executable.feature/library/win32/eclipse*.dll if-no-files-found: error + - name: Upload Windows Test Results + uses: actions/upload-artifact@v3 + with: + name: test-results + if-no-files-found: error + path: '**/target/*-reports/*.xml' + build-launcher-linux64: + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + with: + fetch-depth: 1 + - name: Set up JDK 17 + uses: actions/setup-java@v3 + with: + java-version: '17' + distribution: 'temurin' + cache: maven + - name: Set up Maven + uses: stCarolas/setup-maven@v4.5 + with: + maven-version: 3.9.2 + - name: Install GTK headers + run: sudo apt-get install -y libgtk-3-dev + - name: Build + working-directory: features/org.eclipse.equinox.executable.feature/library/gtk + run: ./build.sh test + shell: bash + - name: Upload artifacts + uses: actions/upload-artifact@v3 + if: success() + with: + name: Linux launcher artifacts + path: | + features/org.eclipse.equinox.executable.feature/library/gtk/eclipse + features/org.eclipse.equinox.executable.feature/library/gtk/eclipse*.so + if-no-files-found: error + - name: Upload Linux Test Results + uses: actions/upload-artifact@v3 + with: + name: test-results + if-no-files-found: error + path: '**/target/*-reports/*.xml' tck: runs-on: ubuntu-latest steps: diff --git a/features/org.eclipse.equinox.executable.feature/library/eclipse.c b/features/org.eclipse.equinox.executable.feature/library/eclipse.c index 4ad3b2053d1..66c80fd00e3 100644 --- a/features/org.eclipse.equinox.executable.feature/library/eclipse.c +++ b/features/org.eclipse.equinox.executable.feature/library/eclipse.c @@ -228,51 +228,6 @@ home directory."); #define OLD_STARTUP _T_ECLIPSE("startup.jar") #define CLASSPATH_PREFIX _T_ECLIPSE("-Djava.class.path=") -/* Define constants for the options recognized by the launcher. */ -#define CONSOLE _T_ECLIPSE("-console") -#define CONSOLELOG _T_ECLIPSE("-consoleLog") -#define DEBUG _T_ECLIPSE("-debug") -#define OS _T_ECLIPSE("-os") -#define OSARCH _T_ECLIPSE("-arch") -#define NOSPLASH _T_ECLIPSE("-nosplash") -#define LAUNCHER _T_ECLIPSE("-launcher") -#define SHOWSPLASH _T_ECLIPSE("-showsplash") -#define EXITDATA _T_ECLIPSE("-exitdata") -#define STARTUP _T_ECLIPSE("-startup") -#define VM _T_ECLIPSE("-vm") -#define WS _T_ECLIPSE("-ws") -#define NAME _T_ECLIPSE("-name") -#define VMARGS _T_ECLIPSE("-vmargs") /* special option processing required */ -#define CP _T_ECLIPSE("-cp") -#define CLASSPATH _T_ECLIPSE("-classpath") -#define JAR _T_ECLIPSE("-jar") -#define PROTECT _T_ECLIPSE("-protect") - -#define OPENFILE _T_ECLIPSE("--launcher.openFile") -#define DEFAULTACTION _T_ECLIPSE("--launcher.defaultAction") -#define TIMEOUT _T_ECLIPSE("--launcher.timeout") -#define LIBRARY _T_ECLIPSE("--launcher.library") -#define SUPRESSERRORS _T_ECLIPSE("--launcher.suppressErrors") -#define INI _T_ECLIPSE("--launcher.ini") -#define APPEND_VMARGS _T_ECLIPSE("--launcher.appendVmargs") -#define OVERRIDE_VMARGS _T_ECLIPSE("--launcher.overrideVmargs") -#define SECOND_THREAD _T_ECLIPSE("--launcher.secondThread") -#define PERM_GEN _T_ECLIPSE("--launcher.XXMaxPermSize") - -#define XXPERMGEN _T_ECLIPSE("-XX:MaxPermSize=") -#define ADDMODULES _T_ECLIPSE("--add-modules") -#define ACTION_OPENFILE _T_ECLIPSE("openFile") -#define GTK_VERSION _T_ECLIPSE("--launcher.GTK_version") - -/* constants for ee options file */ -#define EE_EXECUTABLE _T_ECLIPSE("-Dee.executable=") -#define EE_CONSOLE _T_ECLIPSE("-Dee.executable.console=") -#define EE_VM_LIBRARY _T_ECLIPSE("-Dee.vm.library=") -#define EE_LIBRARY_PATH _T_ECLIPSE("-Dee.library.path=") -#define EE_HOME _T_ECLIPSE("-Dee.home=") -#define EE_FILENAME _T_ECLIPSE("-Dee.filename=") -#define EE_HOME_VAR _T_ECLIPSE("${ee.home}") - /* Splash screen names to look for when -showsplash points to a directory or plugin */ #define SPLASH_IMAGES _T_ECLIPSE("splash.png\0" "splash.jpg\0" "splash.jpeg\0" "splash.gif\0" "splash.bmp\0" "\0") @@ -383,7 +338,7 @@ static _TCHAR* formatVmCommandMsg( _TCHAR* args[], _TCHAR* vmArgs[], _TCHAR* pr static _TCHAR* getDefaultOfficialName(); static _TCHAR* findStartupJar(); static _TCHAR* findSplash(_TCHAR* splashArg); -static _TCHAR** getRelaunchCommand( _TCHAR **vmCommand ); +static _TCHAR** getRelaunchCommand( _TCHAR **newLaucherArgs ); static const _TCHAR* getVMArch(); static int _run(int argc, _TCHAR* argv[], _TCHAR* vmArgs[]); static _TCHAR** mergeConfigurationFilesVMArgs(); @@ -512,7 +467,7 @@ static int _run(int argc, _TCHAR* argv[], _TCHAR* vmArgs[]) _TCHAR* errorMsg = NULL, *msg = NULL; JavaResults* javaResults = NULL; int launchMode; - int running = 1; + int exitCode; /* Initialize official program name */ officialName = name != NULL ? _tcsdup( name ) : getDefaultOfficialName(); @@ -674,137 +629,124 @@ static int _run(int argc, _TCHAR* argv[], _TCHAR* vmArgs[]) vmCommand = buildLaunchCommand(javaVM, vmCommandArgs, progCommandArgs); } - /* While the Java VM should be restarted */ - while(running) - { - msg = formatVmCommandMsg( vmCommand, vmCommandArgs, progCommandArgs ); - if (debug) _tprintf( goVMMsg, msg ); + msg = formatVmCommandMsg( vmCommand, vmCommandArgs, progCommandArgs ); + if (debug) _tprintf( goVMMsg, msg ); - if(launchMode == LAUNCH_JNI) { - javaResults = startJavaVM(jniLib, vmCommandArgs, progCommandArgs, jarFile); - } else { - javaResults = launchJavaVM(vmCommand); - } + if(launchMode == LAUNCH_JNI) { + javaResults = startJavaVM(jniLib, vmCommandArgs, progCommandArgs, jarFile); + } else { + javaResults = launchJavaVM(vmCommand); + } - if (javaResults == NULL) { - /* shouldn't happen, but just in case */ - javaResults = malloc(sizeof(JavaResults)); - javaResults->launchResult = -11; - javaResults->runResult = 0; - javaResults->errorMessage = _tcsdup(javaFailureMsg); - } + if (javaResults == NULL) { + /* shouldn't happen, but just in case */ + javaResults = malloc(sizeof(JavaResults)); + javaResults->launchResult = -11; + javaResults->runResult = 0; + javaResults->errorMessage = _tcsdup(javaFailureMsg); + } - switch( javaResults->launchResult + javaResults->runResult ) { - case 0: /* normal exit */ - running = 0; - break; - case RESTART_LAST_EC: - if (launchMode == LAUNCH_JNI) { - /* copy for relaunch, +1 to ensure NULL terminated */ - relaunchCommand = malloc((initialArgc + 1) * sizeof(_TCHAR*)); - memcpy(relaunchCommand, initialArgv, (initialArgc + 1) * sizeof(_TCHAR*)); - relaunchCommand[initialArgc] = 0; - relaunchCommand[0] = program; - running = 0; - } - break; - - case RESTART_NEW_EC: - if(launchMode == LAUNCH_EXE) { - if (exitData != NULL) free(exitData); - if (getSharedData( sharedID, &exitData ) != 0) - exitData = NULL; - } - if (exitData != 0) { - if (vmCommand != NULL) free( vmCommand ); - vmCommand = parseArgList( exitData ); - if (launchMode == LAUNCH_JNI) { - relaunchCommand = getRelaunchCommand(vmCommand); - running = 0; - } - } else { - running = 0; - if (debug) { - if (!suppressErrors) - displayMessage( officialName, shareMsg ); - else - _ftprintf(stderr, _T_ECLIPSE("%s:\n%s\n"), officialName, shareMsg); - } - } - break; - default: { - _TCHAR *title = _tcsdup(officialName); - running = 0; - errorMsg = NULL; - if (launchMode == LAUNCH_EXE) { - if (exitData != NULL) free(exitData); - if (getSharedData( sharedID, &exitData ) != 0) - exitData = NULL; - } - if (exitData != 0) { - errorMsg = exitData; - exitData = NULL; - if (_tcslen( errorMsg ) > 0) { - _TCHAR *str; - if (_tcsncmp(errorMsg, _T_ECLIPSE(""), _tcslen(_T_ECLIPSE("<title>"))) == 0) { - str = _tcsstr(errorMsg, _T_ECLIPSE("")); - if (str != NULL) { - free( title ); - str[0] = _T_ECLIPSE('\0'); - title = _tcsdup( errorMsg + _tcslen(_T_ECLIPSE("")) ); - str = _tcsdup( str + _tcslen(_T_ECLIPSE("")) ); - free( errorMsg ); - errorMsg = str; - } - } - } - } else { - if (debug) { - if (!suppressErrors) - displayMessage( title, shareMsg ); - else - _ftprintf(stderr, _T_ECLIPSE("%s:\n%s\n"), title, shareMsg); - } - } - if (errorMsg == NULL) { - if (javaResults->runResult) { - /* java was started ok, but returned non-zero exit code */ - errorMsg = malloc( (_tcslen(returnCodeMsg) + _tcslen(msg) + 10) *sizeof(_TCHAR)); - _stprintf(errorMsg, returnCodeMsg,javaResults->runResult, msg); - } else if (javaResults->errorMessage != NULL){ - /* else we had a problem launching java, use custom error message */ - errorMsg = javaResults->errorMessage; - } else { - /* no custom message, use generic message */ - errorMsg = malloc( (_tcslen(exitMsg) + _tcslen(msg) + 10) * sizeof(_TCHAR) ); - _stprintf( errorMsg, exitMsg, javaResults->launchResult, msg ); - } - } - - if (_tcslen(errorMsg) > 0) { - if (!suppressErrors) - displayMessage( title, errorMsg ); - else - _ftprintf(stderr, _T_ECLIPSE("%s:\n%s\n"), title, errorMsg); - } - free( errorMsg ); - free( title ); - break; - } - } - free( msg ); - } + switch( javaResults->launchResult + javaResults->runResult ) { + case 0: /* normal exit */ + break; + case RESTART_LAST_EC: + /* copy for relaunch, +1 to ensure NULL terminated */ + relaunchCommand = malloc((initialArgc + 1) * sizeof(_TCHAR*)); + memcpy(relaunchCommand, initialArgv, (initialArgc + 1) * sizeof(_TCHAR*)); + relaunchCommand[initialArgc] = 0; + relaunchCommand[0] = program; + break; + + case RESTART_NEW_EC: + if(launchMode == LAUNCH_EXE) { + if (exitData != NULL) free(exitData); + if (getSharedData( sharedID, &exitData ) != 0) + exitData = NULL; + } + if (exitData != 0) { + if (vmCommand != NULL) free( vmCommand ); + vmCommand = parseArgList( exitData ); + relaunchCommand = getRelaunchCommand(vmCommand); + } else { + if (debug) { + if (!suppressErrors) + displayMessage( officialName, shareMsg ); + else + _ftprintf(stderr, _T_ECLIPSE("%s:\n%s\n"), officialName, shareMsg); + } + } + break; + default: { + _TCHAR *title = _tcsdup(officialName); + errorMsg = NULL; + if (launchMode == LAUNCH_EXE) { + if (exitData != NULL) free(exitData); + if (getSharedData( sharedID, &exitData ) != 0) + exitData = NULL; + } + if (exitData != 0) { + errorMsg = exitData; + exitData = NULL; + if (_tcslen( errorMsg ) > 0) { + _TCHAR *str; + if (_tcsncmp(errorMsg, _T_ECLIPSE(""), _tcslen(_T_ECLIPSE("<title>"))) == 0) { + str = _tcsstr(errorMsg, _T_ECLIPSE("")); + if (str != NULL) { + free( title ); + str[0] = _T_ECLIPSE('\0'); + title = _tcsdup( errorMsg + _tcslen(_T_ECLIPSE("")) ); + str = _tcsdup( str + _tcslen(_T_ECLIPSE("")) ); + free( errorMsg ); + errorMsg = str; + } + } + } + } else { + if (debug) { + if (!suppressErrors) + displayMessage( title, shareMsg ); + else + _ftprintf(stderr, _T_ECLIPSE("%s:\n%s\n"), title, shareMsg); + } + } + if (errorMsg == NULL) { + if (javaResults->runResult) { + /* java was started ok, but returned non-zero exit code */ + errorMsg = malloc( (_tcslen(returnCodeMsg) + _tcslen(msg) + 10) *sizeof(_TCHAR)); + _stprintf(errorMsg, returnCodeMsg,javaResults->runResult, msg); + } else if (javaResults->errorMessage != NULL){ + /* else we had a problem launching java, use custom error message */ + errorMsg = javaResults->errorMessage; + } else { + /* no custom message, use generic message */ + errorMsg = malloc( (_tcslen(exitMsg) + _tcslen(msg) + 10) * sizeof(_TCHAR) ); + _stprintf( errorMsg, exitMsg, javaResults->launchResult, msg ); + } + } - if(relaunchCommand != NULL) - restartLauncher(NULL, relaunchCommand); + if (_tcslen(errorMsg) > 0) { + if (!suppressErrors) + displayMessage( title, errorMsg ); + else + _ftprintf(stderr, _T_ECLIPSE("%s:\n%s\n"), title, errorMsg); + } + free( errorMsg ); + free( title ); + break; + } + } + free( msg ); - if (launchMode == LAUNCH_JNI) - cleanupVM(javaResults->launchResult ? javaResults->launchResult : javaResults->runResult); + if (sharedID != NULL) { + destroySharedData( sharedID ); + free( sharedID ); + } - if (sharedID != NULL) { - destroySharedData( sharedID ); - free( sharedID ); - } + if(relaunchCommand != NULL) + restartLauncher(NULL, relaunchCommand); + + if (launchMode == LAUNCH_JNI) + cleanupVM(javaResults->launchResult ? javaResults->launchResult : javaResults->runResult); /* Cleanup time. */ free( vmCommandArgs ); @@ -823,10 +765,9 @@ static int _run(int argc, _TCHAR* argv[], _TCHAR* vmArgs[]) if (javaResults == NULL) return -1; - /* reuse the running variable for convenience */ - running = javaResults->launchResult != 0 ? javaResults->launchResult : javaResults->runResult; + exitCode = javaResults->launchResult != 0 ? javaResults->launchResult : javaResults->runResult; free(javaResults); - return running; + return exitCode; } static _TCHAR** buildLaunchCommand( _TCHAR* program, _TCHAR** vmArgs, _TCHAR** progArgs ) { @@ -1522,42 +1463,121 @@ static _TCHAR* findStartupJar(){ } /* - * Return the portion of the vmCommand that should be used for relaunching + * Return the new launcher arguments that should be used for relaunching * * The memory allocated for the command array must be freed */ -static _TCHAR ** getRelaunchCommand( _TCHAR **vmCommand ) +static _TCHAR ** getRelaunchCommand( _TCHAR **newLaucherArgs ) { - int i = -1, req = 0, begin = -1; + int newArgsSize = -1, newVmargsStart = -1, skipOldUserArgs = 0; int idx = 0; _TCHAR ** relaunch; - if (vmCommand == NULL) return NULL; - while(vmCommand[++i] != NULL){ - if ( begin == -1 && _tcsicmp( vmCommand[i], *reqVMarg[req] ) == 0) { - if(reqVMarg[++req] == NULL || *reqVMarg[req] == NULL){ - begin = i + 1; - } + if (newLaucherArgs == NULL) return NULL; + /* + * Visit new args to find + * 1. New args size + * 2. Starting index of new vmargs + * 3. See if old user args should be ignored + */ + while(newLaucherArgs[++newArgsSize] != NULL){ + if (_tcsicmp( newLaucherArgs[newArgsSize], VMARGS ) == 0) { + newVmargsStart = newArgsSize + 1; + } + if (_tcsicmp( newLaucherArgs[newArgsSize], SKIP_OLD_ARGS ) == 0) { + skipOldUserArgs = 1; + } + } + + int oldUserArgsStart = -1, oldUserArgsEnd = -1, oldUserArgsSize = 0; + int oldUserVMArgsStart = -1, oldUserVMArgsEnd = -1; + // Gather the old user args and old user vmargs + for (int i = 1 ; i < initialArgc ; i++) { + if (_tcsicmp(initialArgv[i], OLD_ARGS_START) == 0) { + oldUserArgsStart = i + 1; + } + if (_tcsicmp(initialArgv[i], VMARGS) == 0) { + oldUserVMArgsStart = i + 1; + } + if (_tcsicmp(initialArgv[i], OLD_ARGS_END) == 0) { + oldUserArgsEnd = oldUserVMArgsEnd = i - 1; + if (oldUserArgsStart != -1 && oldUserArgsStart <= oldUserArgsEnd) + oldUserArgsSize = oldUserArgsEnd - oldUserArgsStart + 1; + break; + } + if (i + 1 == initialArgc && oldUserVMArgsStart != -1 && oldUserVMArgsEnd == -1) { + oldUserVMArgsEnd = i; } } - relaunch = malloc((1 + i + 1) * sizeof(_TCHAR *)); + // "--launcher.oldUserArgsStart" is not found in old args which means the launcher was + // invoked by user and its the first restart request after it. Hence track all the args + // provided by user as old user args + if (oldUserArgsStart == -1) { + oldUserArgsStart = 1; + oldUserArgsEnd = initialArgc - 1; + oldUserArgsSize = oldUserArgsEnd - oldUserArgsStart + 1; + } + + relaunch = malloc((1 + (oldUserArgsSize + 2)+ oldUserArgsSize + newArgsSize + 1) * sizeof(_TCHAR *)); + + // Step 1. Add program path relaunch[idx++] = program; - if(begin == -1) { - begin = 1; + + // Step 2. Add old args with --launcher.oldUserArgsStart and --launcher.oldUserArgsEnd + // Over multiple restarts, this is required to keep track of args that were provided by user to launcher first time + relaunch[idx++] = OLD_ARGS_START; + for (int j = oldUserArgsStart; oldUserArgsSize > 0 && j <= oldUserArgsEnd; j++) { + relaunch[idx++] = initialArgv[j]; } - for (i = begin; vmCommand[i] != NULL; i++){ - if (_tcsicmp(vmCommand[i], SHOWSPLASH) == 0) { + relaunch[idx++] = OLD_ARGS_END; + + // Step 3. Add old non-vmargs launch arguments if old args are not skipped + if (skipOldUserArgs == 0) { + for (int j = oldUserArgsStart; oldUserArgsSize > 0 && j <= oldUserArgsEnd; j++) { + if (_tcsicmp(initialArgv[j], VMARGS) == 0) { + break; + } + relaunch[idx++] = initialArgv[j]; + } + } + + // Step 4. Add new non-vmargs launch arguments which will get precedence over old arguments + for (int i = 0; newLaucherArgs[i] != NULL && i != (newVmargsStart - 1); i++){ + if (_tcsicmp(newLaucherArgs[i], SHOWSPLASH) == 0) { /* remove if the next argument is not the bitmap to show */ - if(vmCommand[i + 1] != NULL && vmCommand[i + 1][0] == _T_ECLIPSE('-')) { + if(newLaucherArgs[i + 1] != NULL && newLaucherArgs[i + 1][0] == _T_ECLIPSE('-')) { continue; } - } else if(_tcsncmp(vmCommand[i], CLASSPATH_PREFIX, _tcslen(CLASSPATH_PREFIX)) == 0) { + } else if(_tcsncmp(newLaucherArgs[i], CLASSPATH_PREFIX, _tcslen(CLASSPATH_PREFIX)) == 0) { /* skip -Djava.class.path=... */ continue; + } else if(_tcscmp(newLaucherArgs[i], EXITDATA) == 0) { + /* skip -exitdata argument and value as new shm will be created on relaunch */ + i++; + continue; + } else if(_tcscmp(newLaucherArgs[i], SKIP_OLD_ARGS) == 0) { + /* skip --launcher.skipOldUserArgs */ + continue; } - relaunch[idx++] = vmCommand[i]; + relaunch[idx++] = newLaucherArgs[i]; } + + // Step 5. Add vmargs; first old user vmargs and then new vmargs so that new vmargs get precedence + if ((skipOldUserArgs == 0 && oldUserVMArgsStart != -1) || newVmargsStart != -1) { + relaunch[idx++] = VMARGS; + if (skipOldUserArgs == 0 && oldUserVMArgsStart != -1) { + for (int i = oldUserVMArgsStart; i <= oldUserVMArgsEnd ; i++) { + relaunch[idx++] = initialArgv[i]; + } + } + if (newVmargsStart != -1) { + for (int i = newVmargsStart; newLaucherArgs[i] != NULL; i++) + relaunch[idx++] = newLaucherArgs[i]; + } + } + + // Step 6. place null at the end to indicate end of arguments if(_tcsicmp(relaunch[idx - 1], VMARGS) == 0) relaunch[idx - 1] = NULL; relaunch[idx] = NULL; diff --git a/features/org.eclipse.equinox.executable.feature/library/eclipseCommon.h b/features/org.eclipse.equinox.executable.feature/library/eclipseCommon.h index e956600fb7d..14b362d9388 100644 --- a/features/org.eclipse.equinox.executable.feature/library/eclipseCommon.h +++ b/features/org.eclipse.equinox.executable.feature/library/eclipseCommon.h @@ -18,6 +18,55 @@ #include "eclipseUnicode.h" +/* Define constants for the options recognized by the launcher. */ +#define CONSOLE _T_ECLIPSE("-console") +#define CONSOLELOG _T_ECLIPSE("-consoleLog") +#define DEBUG _T_ECLIPSE("-debug") +#define OS _T_ECLIPSE("-os") +#define OSARCH _T_ECLIPSE("-arch") +#define NOSPLASH _T_ECLIPSE("-nosplash") +#define LAUNCHER _T_ECLIPSE("-launcher") +#define SHOWSPLASH _T_ECLIPSE("-showsplash") +#define EXITDATA _T_ECLIPSE("-exitdata") +#define STARTUP _T_ECLIPSE("-startup") +#define VM _T_ECLIPSE("-vm") +#define WS _T_ECLIPSE("-ws") +#define NAME _T_ECLIPSE("-name") +#define VMARGS _T_ECLIPSE("-vmargs") /* special option processing required */ +#define CP _T_ECLIPSE("-cp") +#define CLASSPATH _T_ECLIPSE("-classpath") +#define JAR _T_ECLIPSE("-jar") +#define PROTECT _T_ECLIPSE("-protect") +#define ROOT _T_ECLIPSE("root") /* the only level of protection we care now */ + +#define OPENFILE _T_ECLIPSE("--launcher.openFile") +#define DEFAULTACTION _T_ECLIPSE("--launcher.defaultAction") +#define TIMEOUT _T_ECLIPSE("--launcher.timeout") +#define LIBRARY _T_ECLIPSE("--launcher.library") +#define SUPRESSERRORS _T_ECLIPSE("--launcher.suppressErrors") +#define INI _T_ECLIPSE("--launcher.ini") +#define APPEND_VMARGS _T_ECLIPSE("--launcher.appendVmargs") +#define OVERRIDE_VMARGS _T_ECLIPSE("--launcher.overrideVmargs") +#define SECOND_THREAD _T_ECLIPSE("--launcher.secondThread") +#define PERM_GEN _T_ECLIPSE("--launcher.XXMaxPermSize") +#define OLD_ARGS_START _T_ECLIPSE("--launcher.oldUserArgsStart") +#define OLD_ARGS_END _T_ECLIPSE("--launcher.oldUserArgsEnd") +#define SKIP_OLD_ARGS _T_ECLIPSE("--launcher.skipOldUserArgs") + +#define XXPERMGEN _T_ECLIPSE("-XX:MaxPermSize=") +#define ADDMODULES _T_ECLIPSE("--add-modules") +#define ACTION_OPENFILE _T_ECLIPSE("openFile") +#define GTK_VERSION _T_ECLIPSE("--launcher.GTK_version") + +/* constants for ee options file */ +#define EE_EXECUTABLE _T_ECLIPSE("-Dee.executable=") +#define EE_CONSOLE _T_ECLIPSE("-Dee.executable.console=") +#define EE_VM_LIBRARY _T_ECLIPSE("-Dee.vm.library=") +#define EE_LIBRARY_PATH _T_ECLIPSE("-Dee.library.path=") +#define EE_HOME _T_ECLIPSE("-Dee.home=") +#define EE_FILENAME _T_ECLIPSE("-Dee.filename=") +#define EE_HOME_VAR _T_ECLIPSE("${ee.home}") + /* Variables and Methods that will be needed by both the executable and the library */ #define MAX_PATH_LENGTH 2000 diff --git a/features/org.eclipse.equinox.executable.feature/library/eclipseMain.c b/features/org.eclipse.equinox.executable.feature/library/eclipseMain.c index f31bd7e36b6..9cd975b7bd3 100644 --- a/features/org.eclipse.equinox.executable.feature/library/eclipseMain.c +++ b/features/org.eclipse.equinox.executable.feature/library/eclipseMain.c @@ -45,15 +45,6 @@ static _TCHAR* rootMsg = _T_ECLIPSE("The %s executable launcher is configured to not start with \n\ administrative privileges."); -#define NAME _T_ECLIPSE("-name") -#define VMARGS _T_ECLIPSE("-vmargs") /* special option processing required */ -/* New arguments have the form --launcher. to avoid collisions */ -#define LIBRARY _T_ECLIPSE("--launcher.library") -#define SUPRESSERRORS _T_ECLIPSE("--launcher.suppressErrors") -#define INI _T_ECLIPSE("--launcher.ini") -#define PROTECT _T_ECLIPSE("-protect") /* This argument is also handled in eclipse.c for Mac specific processing */ -#define ROOT _T_ECLIPSE("root") /* the only level of protection we care now */ - /* this typedef must match the run method in eclipse.c */ typedef int (*RunMethod)(int argc, _TCHAR* argv[], _TCHAR* vmArgs[]); typedef void (*SetInitialArgs)(int argc, _TCHAR*argv[], _TCHAR* library, int consoleLauncher); @@ -284,13 +275,20 @@ static _TCHAR* findProgram(_TCHAR* argv[]) { static void parseArgs( int* pArgc, _TCHAR* argv[], int useVMargs ) { int index; + int skipOldArgs = 0; /* Ensure the list of user argument is NULL terminated. */ argv[ *pArgc ] = NULL; /* For each user defined argument */ for (index = 0; index < *pArgc; index++){ - if(_tcsicmp(argv[index], VMARGS) == 0) { + if (_tcsicmp(argv[index], OLD_ARGS_START) == 0) { + skipOldArgs = 1; + } else if (_tcsicmp(argv[index], OLD_ARGS_END) == 0) { + skipOldArgs = 0; + } else if (skipOldArgs) { + continue; + } else if(_tcsicmp(argv[index], VMARGS) == 0) { if (useVMargs == 1) { //Use the VMargs as the user specified vmArgs userVMarg = &argv[ index+1 ]; } @@ -306,7 +304,7 @@ static void parseArgs( int* pArgc, _TCHAR* argv[], int useVMargs ) if(_tcsicmp(argv[++index], ROOT) == 0){ protectRoot = 1; } - } + } } } @@ -336,17 +334,40 @@ static _TCHAR* checkForIni(int argc, _TCHAR* argv[]) */ static int createUserArgs(int configArgc, _TCHAR **configArgv, int *argc, _TCHAR ***argv) { - _TCHAR** newArray = (_TCHAR **)malloc((configArgc + *argc + 1) * sizeof(_TCHAR *)); + int argsSize = configArgc + *argc; + int skipOldArgs = 0; + _TCHAR** newArray = (_TCHAR **)malloc((argsSize + 1) * sizeof(_TCHAR *)); newArray[0] = (*argv)[0]; /* use the original argv[0] */ memcpy(newArray + 1, configArgv, configArgc * sizeof(_TCHAR *)); + int startIndex = 1 + configArgc; /* Skip the argument zero (program path and name) */ - memcpy(newArray + 1 + configArgc, *argv + 1, (*argc - 1) * sizeof(_TCHAR *)); + for (int i = 1 ; i < *argc ; i++) { + if (_tcsicmp((*argv)[i], OLD_ARGS_START) == 0) { + argsSize--; + skipOldArgs = 1; + continue; + } + if (_tcsicmp((*argv)[i], OLD_ARGS_END) == 0) { + argsSize--; + skipOldArgs = 0; + continue; + } + if (skipOldArgs) { + argsSize--; + continue; + } + if (_tcscmp((*argv)[i], SKIP_OLD_ARGS) == 0) { + argsSize--; + continue; + } + newArray[startIndex++] = (*argv)[i]; + } /* Null terminate the new list of arguments and return it. */ *argv = newArray; - *argc += configArgc; + *argc = argsSize; (*argv)[*argc] = NULL; return 0; diff --git a/features/org.eclipse.equinox.executable.feature/library/gtk/build.sh b/features/org.eclipse.equinox.executable.feature/library/gtk/build.sh index f837c0328af..02e459530a6 100755 --- a/features/org.eclipse.equinox.executable.feature/library/gtk/build.sh +++ b/features/org.eclipse.equinox.executable.feature/library/gtk/build.sh @@ -16,7 +16,7 @@ # Martin Oberhuber (Wind River) - [517013] Avoid memcpy@GLIBC_2.14 dependency #******************************************************************************* # -# Usage: sh build.sh [] [clean] +# Usage: sh build.sh [] [clean] [test] # # where the optional switches are: # -output - executable filename ("eclipse") @@ -30,6 +30,8 @@ # # Examples: # sh build.sh clean +# sh build.sh test +# sh build.sh clean test # sh build.sh -java /usr/j2se OPTFLAG=-g PICFLAG=-fpic cd `dirname $0` diff --git a/features/org.eclipse.equinox.executable.feature/library/gtk/make_linux.mak b/features/org.eclipse.equinox.executable.feature/library/gtk/make_linux.mak index b9238d51a93..a0f4a8868a9 100644 --- a/features/org.eclipse.equinox.executable.feature/library/gtk/make_linux.mak +++ b/features/org.eclipse.equinox.executable.feature/library/gtk/make_linux.mak @@ -43,7 +43,8 @@ PROGRAM_LIBRARY = $(PROGRAM_OUTPUT)_$(LIB_VERSION).so EXEC_DIR ?= ../../../../../rt.equinox.binaries/org.eclipse.equinox.executable OUTPUT_DIR ?= $(EXEC_DIR)/bin/$(DEFAULT_WS)/$(DEFAULT_OS)/$(DEFAULT_OS_ARCH) -LIBRARY_DIR ?= $(EXEC_DIR)/../org.eclipse.equinox.launcher.$(DEFAULT_WS).$(DEFAULT_OS).$(DEFAULT_OS_ARCH) +LIBRARY_FRAGMENT_NAME ?= org.eclipse.equinox.launcher.$(DEFAULT_WS).$(DEFAULT_OS).$(DEFAULT_OS_ARCH) +LIBRARY_DIR ?= $(EXEC_DIR)/../$(LIBRARY_FRAGMENT_NAME) # 64 bit specific flag: ifeq ($(M_CFLAGS),) @@ -147,16 +148,21 @@ clean: # Convienience method to install produced output into a developer's eclipse for testing/development. dev_build_install: all -ifeq "$(origin DEV_ECLIPSE)" "environment" +ifneq ($(filter "$(origin DEV_ECLIPSE)", "environment" "command line"),) $(info Copying $(EXEC) and $(DLL) into your development eclipse folder:) mkdir -p ${DEV_ECLIPSE}/ cp $(EXEC) ${DEV_ECLIPSE}/ - mkdir -p ${DEV_ECLIPSE}/plugins/org.eclipse.equinox.launcher.gtk.linux.x86_64/ - cp $(DLL) ${DEV_ECLIPSE}/plugins/org.eclipse.equinox.launcher.gtk.linux.x86_64/ + mkdir -p ${DEV_ECLIPSE}/plugins/$(LIBRARY_FRAGMENT_NAME)/ + cp $(DLL) ${DEV_ECLIPSE}/plugins/$(LIBRARY_FRAGMENT_NAME)/ else $(error $(DEV_INSTALL_ERROR_MSG)) endif +test: + mvn -f ../org.eclipse.launcher.tests/pom.xml clean verify -Dmaven.test.skip=true + make -f $(firstword $(MAKEFILE_LIST)) dev_build_install LIBRARY_FRAGMENT_NAME=org.eclipse.equinox.launcher DEV_ECLIPSE=../org.eclipse.launcher.tests/target/test-run + mvn -f ../org.eclipse.launcher.tests/pom.xml test + define DEV_INSTALL_ERROR_MSG = Note: DEV_ECLIPSE environmental variable is not defined. diff --git a/features/org.eclipse.equinox.executable.feature/library/org.eclipse.launcher.tests/.classpath b/features/org.eclipse.equinox.executable.feature/library/org.eclipse.launcher.tests/.classpath new file mode 100644 index 00000000000..3e7d5e55fff --- /dev/null +++ b/features/org.eclipse.equinox.executable.feature/library/org.eclipse.launcher.tests/.classpath @@ -0,0 +1,21 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/features/org.eclipse.equinox.executable.feature/library/org.eclipse.launcher.tests/.gitignore b/features/org.eclipse.equinox.executable.feature/library/org.eclipse.launcher.tests/.gitignore new file mode 100644 index 00000000000..ae3c1726048 --- /dev/null +++ b/features/org.eclipse.equinox.executable.feature/library/org.eclipse.launcher.tests/.gitignore @@ -0,0 +1 @@ +/bin/ diff --git a/features/org.eclipse.equinox.executable.feature/library/org.eclipse.launcher.tests/.project b/features/org.eclipse.equinox.executable.feature/library/org.eclipse.launcher.tests/.project new file mode 100644 index 00000000000..f135598dbeb --- /dev/null +++ b/features/org.eclipse.equinox.executable.feature/library/org.eclipse.launcher.tests/.project @@ -0,0 +1,23 @@ + + + org.eclipse.launcher.tests + + + + + + org.eclipse.jdt.core.javabuilder + + + + + org.eclipse.m2e.core.maven2Builder + + + + + + org.eclipse.m2e.core.maven2Nature + org.eclipse.jdt.core.javanature + + diff --git a/features/org.eclipse.equinox.executable.feature/library/org.eclipse.launcher.tests/.settings/org.eclipse.jdt.core.prefs b/features/org.eclipse.equinox.executable.feature/library/org.eclipse.launcher.tests/.settings/org.eclipse.jdt.core.prefs new file mode 100644 index 00000000000..43c8d716b15 --- /dev/null +++ b/features/org.eclipse.equinox.executable.feature/library/org.eclipse.launcher.tests/.settings/org.eclipse.jdt.core.prefs @@ -0,0 +1,15 @@ +eclipse.preferences.version=1 +org.eclipse.jdt.core.compiler.codegen.inlineJsrBytecode=enabled +org.eclipse.jdt.core.compiler.codegen.targetPlatform=17 +org.eclipse.jdt.core.compiler.codegen.unusedLocal=preserve +org.eclipse.jdt.core.compiler.compliance=17 +org.eclipse.jdt.core.compiler.debug.lineNumber=generate +org.eclipse.jdt.core.compiler.debug.localVariable=generate +org.eclipse.jdt.core.compiler.debug.sourceFile=generate +org.eclipse.jdt.core.compiler.problem.assertIdentifier=error +org.eclipse.jdt.core.compiler.problem.enablePreviewFeatures=disabled +org.eclipse.jdt.core.compiler.problem.enumIdentifier=error +org.eclipse.jdt.core.compiler.problem.forbiddenReference=warning +org.eclipse.jdt.core.compiler.problem.reportPreviewFeatures=warning +org.eclipse.jdt.core.compiler.release=enabled +org.eclipse.jdt.core.compiler.source=17 diff --git a/features/org.eclipse.equinox.executable.feature/library/org.eclipse.launcher.tests/.settings/org.eclipse.m2e.core.prefs b/features/org.eclipse.equinox.executable.feature/library/org.eclipse.launcher.tests/.settings/org.eclipse.m2e.core.prefs new file mode 100644 index 00000000000..f897a7f1cb2 --- /dev/null +++ b/features/org.eclipse.equinox.executable.feature/library/org.eclipse.launcher.tests/.settings/org.eclipse.m2e.core.prefs @@ -0,0 +1,4 @@ +activeProfiles= +eclipse.preferences.version=1 +resolveWorkspaceProjects=true +version=1 diff --git a/features/org.eclipse.equinox.executable.feature/library/org.eclipse.launcher.tests/pom.xml b/features/org.eclipse.equinox.executable.feature/library/org.eclipse.launcher.tests/pom.xml new file mode 100644 index 00000000000..1d7aa527df1 --- /dev/null +++ b/features/org.eclipse.equinox.executable.feature/library/org.eclipse.launcher.tests/pom.xml @@ -0,0 +1,58 @@ + + 4.0.0 + org.eclipse.launcher.tests + org.eclipse.launcher.tests + 1.0.0 + jar + + + org.junit.jupiter + junit-jupiter-engine + 5.10.0 + + + + src + test.launcher + + + org.apache.maven.plugins + maven-compiler-plugin + 3.11.0 + + 17 + + + + org.apache.maven.plugins + maven-jar-plugin + 3.3.0 + + + default-jar + compile + + jar + + + + + main.TestLauncherApp + + + + + + + + org.apache.maven.plugins + maven-surefire-plugin + 3.1.2 + + ${project.build.directory}/test-run + true + + + + + \ No newline at end of file diff --git a/features/org.eclipse.equinox.executable.feature/library/org.eclipse.launcher.tests/src/main/TestLauncherApp.java b/features/org.eclipse.equinox.executable.feature/library/org.eclipse.launcher.tests/src/main/TestLauncherApp.java new file mode 100644 index 00000000000..0e868cd21a1 --- /dev/null +++ b/features/org.eclipse.equinox.executable.feature/library/org.eclipse.launcher.tests/src/main/TestLauncherApp.java @@ -0,0 +1,112 @@ +/******************************************************************************* + * Copyright (c) 2023 Eclipse Foundation, Inc. and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Umair Sair - initial API and implementation + *******************************************************************************/ +package main; + +import java.io.BufferedReader; +import java.io.DataOutputStream; +import java.io.InputStreamReader; +import java.net.InetSocketAddress; +import java.net.Socket; +import java.util.ArrayList; +import java.util.List; + +import org.eclipse.equinox.launcher.JNIBridge; + +/** + * Dummy test application used for eclipse launcher testing. JUnit tests + * launches the eclipse launcher with startup application pointing to the jar file + * of this application. Test starts a server and set the port number via + * 'eclipse_test_port' environment variable. This application connects to that + * port and accepts following queries + * - -args - returns the arguments passed to this application. Test verify if the + * arguments are passed to application correctly by the launcher. + * + * And accepts following information + * - -exitdata - The exit data this application should set before exiting + * - -exitcode - The exit code with which this application should exit + * + * @author umairsair + * + */ +public class TestLauncherApp { + + private static JNIBridge bridge; + private static String sharedId; + private static String[] args; + private static List exitData = new ArrayList<>(); + private static int exitCode = 0; + + + public int run(String[] args) { + parseArgs(args); + return exitCode; + } + + public static void main(String[] args) { + parseArgs(args); + + System.exit(exitCode); + } + + private static void parseArgs(String[] args) { + TestLauncherApp.args = args; + for (int i = 0; i < args.length; i++) { + if ("--launcher.library".equals(args[i])) { + try { + bridge = new JNIBridge(args[i + 1]); + } catch (Exception e) { + e.printStackTrace(); + } + } + if ("-exitdata".equals(args[i])) { + sharedId = args[i + 1]; + } + } + + try { + communicateToServer(); + } catch (Exception e) { + e.printStackTrace(); + } + + if (!exitData.isEmpty()) { + bridge.setExitData(sharedId, String.join("\n", exitData) + "\n"); + } + } + + private static void communicateToServer() throws Exception { + String port = System.getenv(TestLauncherConstants.PORT_ENV_KEY); + try (Socket socket = new Socket()) { + socket.connect(new InetSocketAddress("localhost", Integer.parseInt(port)), 10000); + BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); + DataOutputStream out = new DataOutputStream(socket.getOutputStream()); + String line; + while ((line = in.readLine()) != null) { + if (TestLauncherConstants.ARGS_PARAMETER.equals(line)) { + out.writeBytes(String.join("\n", args) + "\n" + TestLauncherConstants.MULTILINE_ARG_VALUE_TERMINATOR + "\n"); + out.flush(); + } else if (TestLauncherConstants.EXITDATA_PARAMETER.equals(line)) { + while ((line = in.readLine()) != null) { + if (TestLauncherConstants.MULTILINE_ARG_VALUE_TERMINATOR.equals(line)) + break; + exitData.add(line); + } + } else if (TestLauncherConstants.EXITCODE_PARAMETER.equals(line)) { + TestLauncherApp.exitCode = Integer.parseInt(in.readLine()); + break; + } + } + } + } +} diff --git a/features/org.eclipse.equinox.executable.feature/library/org.eclipse.launcher.tests/src/main/TestLauncherConstants.java b/features/org.eclipse.equinox.executable.feature/library/org.eclipse.launcher.tests/src/main/TestLauncherConstants.java new file mode 100644 index 00000000000..5bd1e0bed41 --- /dev/null +++ b/features/org.eclipse.equinox.executable.feature/library/org.eclipse.launcher.tests/src/main/TestLauncherConstants.java @@ -0,0 +1,23 @@ +/******************************************************************************* + * Copyright (c) 2023 Eclipse Foundation, Inc. and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Umair Sair - initial API and implementation + *******************************************************************************/ +package main; + +public interface TestLauncherConstants { + public static final String ARGS_PARAMETER = "-args"; //$NON-NLS-1$ + public static final String EXITDATA_PARAMETER = "-exitdata"; //$NON-NLS-1$ + public static final String EXITCODE_PARAMETER = "-exitcode"; //$NON-NLS-1$ + public static final String MULTILINE_ARG_VALUE_TERMINATOR = "---"; //$NON-NLS-1$ + + public static final String PORT_ENV_KEY = "eclipse_test_port"; //$NON-NLS-1$ +} diff --git a/features/org.eclipse.equinox.executable.feature/library/org.eclipse.launcher.tests/src/org/eclipse/equinox/launcher/JNIBridge.java b/features/org.eclipse.equinox.executable.feature/library/org.eclipse.launcher.tests/src/org/eclipse/equinox/launcher/JNIBridge.java new file mode 120000 index 00000000000..7a2f31094d8 --- /dev/null +++ b/features/org.eclipse.equinox.executable.feature/library/org.eclipse.launcher.tests/src/org/eclipse/equinox/launcher/JNIBridge.java @@ -0,0 +1 @@ +../../../../../../../../../bundles/org.eclipse.equinox.launcher/src/org/eclipse/equinox/launcher/JNIBridge.java \ No newline at end of file diff --git a/features/org.eclipse.equinox.executable.feature/library/org.eclipse.launcher.tests/src/test/java/LauncherTests.java b/features/org.eclipse.equinox.executable.feature/library/org.eclipse.launcher.tests/src/test/java/LauncherTests.java new file mode 100644 index 00000000000..1baaeaf66a5 --- /dev/null +++ b/features/org.eclipse.equinox.executable.feature/library/org.eclipse.launcher.tests/src/test/java/LauncherTests.java @@ -0,0 +1,483 @@ +/******************************************************************************* + * Copyright (c) 2023 Eclipse Foundation, Inc. and others. + * + * This program and the accompanying materials + * are made available under the terms of the Eclipse Public License 2.0 + * which accompanies this distribution, and is available at + * https://www.eclipse.org/legal/epl-2.0/ + * + * SPDX-License-Identifier: EPL-2.0 + * + * Contributors: + * Umair Sair - initial API and implementation + *******************************************************************************/ +package test.java; + +import static org.junit.jupiter.api.Assertions.assertEquals; +import static org.junit.jupiter.api.Assertions.assertFalse; +import static org.junit.jupiter.api.Assertions.assertTrue; + +import java.io.BufferedReader; +import java.io.DataOutputStream; +import java.io.File; +import java.io.FileWriter; +import java.io.IOException; +import java.io.InputStreamReader; +import java.net.ServerSocket; +import java.net.Socket; +import java.util.ArrayList; +import java.util.Arrays; +import java.util.Collections; +import java.util.List; +import java.util.concurrent.TimeUnit; +import java.util.stream.Collectors; + +import org.junit.jupiter.api.AfterEach; +import org.junit.jupiter.api.BeforeEach; +import org.junit.jupiter.api.Test; + +import main.TestLauncherConstants; + +public class LauncherTests { + private static final String ECLIPSE_INI_FILE_NAME = "eclipse.ini"; + // @formatter:off + private static final String DEFAULT_ECLIPSE_INI_CONTENT = "-startup\n" + + "../test.launcher.jar\n" + + "--launcher.library\n" + + "plugins/org.eclipse.equinox.launcher\n" + + "-vmargs\n" + + "-Xms40m\n" + + ""; + // @formatter:on + public static final Integer EXIT_OK = Integer.valueOf(0); + public static final Integer EXIT_RESTART = Integer.valueOf(23); + public static final Integer EXIT_RELAUNCH = Integer.valueOf(24); + + private ServerSocket server; + + @BeforeEach + void setUp() throws IOException { + server = new ServerSocket(0, 1); + server.setSoTimeout(10000); + } + + @AfterEach + void tearDown() throws IOException { + if (server != null) { + server.close(); + server = null; + } + } + + @Test + void test_appTerminatesWithCodeZeroOnExit() throws IOException, InterruptedException { + writeEclipseIni(DEFAULT_ECLIPSE_INI_CONTENT); + + Process launcherProcess = startEclipseLauncher(Collections.emptyList()); + + Socket socket = server.accept(); + + List appArgs = new ArrayList<>(); + analyzeLaunchedTestApp(socket, appArgs, null, EXIT_OK); + + // Make sure arguments contain default vmargs + assertTrue(appArgs.containsAll(Arrays.asList("-vmargs", "-Xms40m"))); + // Make sure launcher exited with code zero + launcherProcess.waitFor(5, TimeUnit.SECONDS); + assertTrue(launcherProcess.exitValue() == 0); + } + + @Test + void test_eclipseIniChangesShouldBePickedOnRestart() throws IOException { + writeEclipseIni(DEFAULT_ECLIPSE_INI_CONTENT); + + startEclipseLauncher(Collections.emptyList()); + + Socket socket = server.accept(); + + // Before restarting, update eclipse.ini and check if extra arg is read + writeEclipseIni(DEFAULT_ECLIPSE_INI_CONTENT + "-Dtest"); + + List appArgs1 = new ArrayList<>(); + analyzeLaunchedTestApp(socket, appArgs1, null, EXIT_RESTART); + + socket = server.accept(); + + List appArgs2 = new ArrayList<>(); + analyzeLaunchedTestApp(socket, appArgs2, null, EXIT_OK); + + // Args after restart contains new argument in eclipse.ini + assertTrue(appArgs2.contains("-Dtest")); + + // Other than exitdata arg, all other args should be same over restarts + appArgs1.remove(appArgs1.indexOf(TestLauncherConstants.EXITDATA_PARAMETER) + 1); + appArgs2.remove(appArgs2.indexOf(TestLauncherConstants.EXITDATA_PARAMETER) + 1); + // After restart, only -Dtest arg should be extra, other than that args should + // be same + appArgs2.remove(appArgs2.indexOf("-Dtest")); + + // Convert backslashes to forward slashes before comparison so that all paths are consistent + assertEquals(appArgs1.stream().map(s -> s.replace('\\', '/')).collect(Collectors.toList()), + appArgs2.stream().map(s -> s.replace('\\', '/')).collect(Collectors.toList())); + } + + @Test + void test_eclipseIniChangesShouldBePickedOnRelaunch() throws IOException { + writeEclipseIni(DEFAULT_ECLIPSE_INI_CONTENT); + + startEclipseLauncher(Collections.emptyList()); + + Socket socket = server.accept(); + + // Before relaunching, update eclipse.ini and check if extra arg is read + writeEclipseIni(DEFAULT_ECLIPSE_INI_CONTENT + "-Dtest"); + + List appArgs1 = new ArrayList<>(); + analyzeLaunchedTestApp(socket, appArgs1, "-data\ndir1", EXIT_RELAUNCH); + + socket = server.accept(); + + List appArgs2 = new ArrayList<>(); + analyzeLaunchedTestApp(socket, appArgs2, null, EXIT_OK); + + // Args after relaunch contains new argument in eclipse.ini + assertTrue(appArgs2.contains("-Dtest")); + + // Other than exitdata arg, all other args should be same over relaunches + appArgs1.remove(appArgs1.indexOf(TestLauncherConstants.EXITDATA_PARAMETER) + 1); + appArgs2.remove(appArgs2.indexOf(TestLauncherConstants.EXITDATA_PARAMETER) + 1); + // After relaunch, -Dtest and -data args should be extra, other than that args should + // be same + appArgs2.remove(appArgs2.indexOf("-Dtest")); + appArgs2.remove(appArgs2.indexOf("-data")); + appArgs2.remove(appArgs2.indexOf("dir1")); + + // Convert backslashes to forward slashes before comparison so that all paths are consistent + assertEquals(appArgs1.stream().map(s -> s.replace('\\', '/')).collect(Collectors.toList()), + appArgs2.stream().map(s -> s.replace('\\', '/')).collect(Collectors.toList())); + } + + @Test + void test_newNonVMArgsForRelaunchShouldBeEffective() throws IOException { + writeEclipseIni(DEFAULT_ECLIPSE_INI_CONTENT); + + startEclipseLauncher(Collections.emptyList()); + + Socket socket = server.accept(); + + List appArgs1 = new ArrayList<>(); + analyzeLaunchedTestApp(socket, appArgs1, "-data\ndir1", EXIT_RELAUNCH); + + socket = server.accept(); + + List appArgs2 = new ArrayList<>(); + analyzeLaunchedTestApp(socket, appArgs2, null, EXIT_OK); + + // Make sure on relaunch, new args is provided + assertTrue(appArgs2.contains("-data")); + assertTrue(appArgs2.contains("dir1")); + } + + @Test + void test_newNonVMArgsForRelaunchShouldOverrideOlderSameArg() throws IOException { + writeEclipseIni(DEFAULT_ECLIPSE_INI_CONTENT); + + // Start eclipse with arguments '-data dir1' + startEclipseLauncher(List.of("-data", "dir1")); + + Socket socket = server.accept(); + + List appArgs1 = new ArrayList<>(); + // Relaunch should provide arguments '-data dir2' + analyzeLaunchedTestApp(socket, appArgs1, "-data\ndir2", EXIT_RELAUNCH); + + socket = server.accept(); + + List appArgs2 = new ArrayList<>(); + analyzeLaunchedTestApp(socket, appArgs2, null, EXIT_OK); + + // Relaunched app should contain '-data dir1 -data dir2' + assertEquals(Collections.frequency(appArgs2, "-data"), 2); + assertTrue(appArgs2.contains("dir1")); + assertTrue(appArgs2.contains("dir2")); + // dir2 argument should appear later so that it gets effective + assertTrue(appArgs2.indexOf("dir2") > appArgs2.indexOf("dir1")); + } + + @Test + void test_newNonVMArgsForRelaunchWithSkipOldUserArgs() throws IOException { + writeEclipseIni(DEFAULT_ECLIPSE_INI_CONTENT); + + // Start eclipse with arguments '-data dir1' + startEclipseLauncher(List.of("-data", "dir1")); + + Socket socket = server.accept(); + + List appArgs1 = new ArrayList<>(); + // Relaunch should provide arguments '-data dir2' and + // '--launcher.skipOldUserArgs' so that 'data dir1 is not + // provided on relaunch + analyzeLaunchedTestApp(socket, appArgs1, "-data\ndir2\n--launcher.skipOldUserArgs", EXIT_RELAUNCH); + + socket = server.accept(); + + List appArgs2 = new ArrayList<>(); + analyzeLaunchedTestApp(socket, appArgs2, null, EXIT_OK); + + assertTrue(appArgs1.contains("-data")); + assertTrue(appArgs1.contains("dir1")); + + // -data argument should be once + assertEquals(1, Collections.frequency(appArgs2, "-data")); + // -data argument should exist with only value dir2 + assertTrue(appArgs2.contains("dir2")); + assertFalse(appArgs2.contains("dir1")); + } + + @Test + void test_newVMArgsForRelaunchShouldBeEffective() throws IOException { + writeEclipseIni(DEFAULT_ECLIPSE_INI_CONTENT); + + // Start eclipse with arguments '-vmargs -Dtest=1'. Note that by default + // --launcher.overrideVmargs is set + startEclipseLauncher(List.of("-vmargs", "-Dtest=1")); + + Socket socket = server.accept(); + + List appArgs1 = new ArrayList<>(); + // Relaunch should provide arguments '-vmargs -Dtest=2' + analyzeLaunchedTestApp(socket, appArgs1, "-vmargs\n-Dtest=2", EXIT_RELAUNCH); + + socket = server.accept(); + + List appArgs2 = new ArrayList<>(); + analyzeLaunchedTestApp(socket, appArgs2, null, EXIT_OK); + + // First launch of eclipse should not have vmargs provided by eclipse.ini i.e., + // -Xms40m + // and have the argument mentioned on commandline i.e., -Dtest=1 + assertFalse(appArgs1.contains("-Xms40m")); + assertTrue(appArgs1.contains("-Dtest=1")); + + // After relaunch, vmargs should also contain -Dtest=2 and it should appear + // after -Dtest=1 + assertFalse(appArgs2.contains("-Xms40m")); + assertTrue(appArgs2.contains("-Dtest=1")); + assertTrue(appArgs2.contains("-Dtest=2")); + assertTrue(appArgs2.indexOf("-Dtest=2") > appArgs2.indexOf("-Dtest=1")); + } + + @Test + void test_newVMArgsForRelaunchhWithSkipOldUserArgs() throws IOException { + writeEclipseIni(DEFAULT_ECLIPSE_INI_CONTENT); + + // Start eclipse with arguments '-vmargs -Dtest=1'. Note that by default + // --launcher.overrideVmargs is set + startEclipseLauncher(List.of("-vmargs", "-Dtest=1")); + + Socket socket = server.accept(); + + List appArgs1 = new ArrayList<>(); + // Relaunch should provide arguments '-vmargs -Dtest=2' and + // '--launcher.skipOldUserArgs' + analyzeLaunchedTestApp(socket, appArgs1, "--launcher.skipOldUserArgs\n-vmargs\n-Dtest=2", EXIT_RELAUNCH); + + socket = server.accept(); + + List appArgs2 = new ArrayList<>(); + analyzeLaunchedTestApp(socket, appArgs2, null, EXIT_OK); + + // First launch of eclipse should not have vmargs provided by eclipse.ini i.e., + // -Xms40m + // and have the argument mentioned on commandline i.e., -Dtest=1 + assertFalse(appArgs1.contains("-Xms40m")); + assertTrue(appArgs1.contains("-Dtest=1")); + + // After relaunch, vmargs should only contain -Dtest=2 + assertFalse(appArgs2.contains("-Xms40m")); + assertFalse(appArgs2.contains("-Dtest=1")); + assertTrue(appArgs2.contains("-Dtest=2")); + } + + @Test + void test_newVMArgsForRelaunchhWithAppendVMArgs() throws IOException { + writeEclipseIni(DEFAULT_ECLIPSE_INI_CONTENT + "-Dtest=0"); + + // Start eclipse with arguments '-vmargs -Dtest=1'. Note that by default + // --launcher.overrideVmargs is set + startEclipseLauncher(List.of("-vmargs", "-Dtest=1")); + + Socket socket = server.accept(); + + List appArgs1 = new ArrayList<>(); + // Relaunch should provide arguments '-vmargs -Dtest=2' and + // '--launcher.appendVmargs' + analyzeLaunchedTestApp(socket, appArgs1, "--launcher.appendVmargs\n-vmargs\n-Dtest=2", EXIT_RELAUNCH); + + socket = server.accept(); + + List appArgs2 = new ArrayList<>(); + analyzeLaunchedTestApp(socket, appArgs2, null, EXIT_OK); + + // First launch of eclipse should not have vmargs provided by eclipse.ini i.e., + // -Xms40m and -Dtest=0 + // and have the argument mentioned on commandline i.e., -Dtest=1 + assertFalse(appArgs1.contains("-Xms40m")); + assertFalse(appArgs1.contains("-Dtest=0")); + assertTrue(appArgs1.contains("-Dtest=1")); + + // After relaunch, vmargs should contain all args; args from commandline, + // eclipse.ini and from args provided by relaunch + assertTrue(appArgs2.contains("-Xms40m")); + assertTrue(appArgs2.contains("-Dtest=0")); + assertTrue(appArgs2.contains("-Dtest=1")); + assertTrue(appArgs2.contains("-Dtest=2")); + // @formatter:off + // -Dtest args should appear in order as + // - from eclipse.ini + // - then from commandline + // - then from relaunch + // Hence relaunch one will be effective + // @formatter:on + assertTrue(appArgs2.indexOf("-Dtest=2") > appArgs2.indexOf("-Dtest=1")); + assertTrue(appArgs2.indexOf("-Dtest=1") > appArgs2.indexOf("-Dtest=0")); + } + + @Test + void test_newVMArgsForRelaunchhWithAppendVMArgsAndSkipOldUserArgs() throws IOException { + writeEclipseIni(DEFAULT_ECLIPSE_INI_CONTENT + "-Dtest=0"); + + // Start eclipse with arguments '-vmargs -Dtest=1'. Note that by default + // --launcher.overrideVmargs is set + startEclipseLauncher(List.of("-vmargs", "-Dtest=1")); + + Socket socket = server.accept(); + + List appArgs1 = new ArrayList<>(); + // Relaunch should provide arguments '-vmargs -Dtest=2', + // '--launcher.appendVmargs' and '--launcher.skipOldUserArgs' + analyzeLaunchedTestApp(socket, appArgs1, + "--launcher.appendVmargs\n--launcher.skipOldUserArgs\n-vmargs\n-Dtest=2", EXIT_RELAUNCH); + + socket = server.accept(); + + List appArgs2 = new ArrayList<>(); + analyzeLaunchedTestApp(socket, appArgs2, null, EXIT_OK); + + // First launch of eclipse should not have vmargs provided by eclipse.ini i.e., + // -Xms40m and -Dtest=0 + // and have the argument mentioned on commandline i.e., -Dtest=1 + assertFalse(appArgs1.contains("-Xms40m")); + assertFalse(appArgs1.contains("-Dtest=0")); + assertTrue(appArgs1.contains("-Dtest=1")); + + // After restart, user provided arg should be ignore and only eclipse.ini and + // relaunch provided args should exist + assertTrue(appArgs2.contains("-Xms40m")); + assertTrue(appArgs2.contains("-Dtest=0")); + assertFalse(appArgs2.contains("-Dtest=1")); // provided from commandline and doesn't exist on restart + assertTrue(appArgs2.contains("-Dtest=2")); + assertTrue(appArgs2.indexOf("-Dtest=2") > appArgs2.indexOf("-Dtest=0")); + } + + @Test + void test_ArgsRemainSameOverRestarts() throws IOException { + writeEclipseIni(DEFAULT_ECLIPSE_INI_CONTENT); + + startEclipseLauncher(Collections.emptyList()); + + Socket socket = server.accept(); + + List appArgs1 = new ArrayList<>(); + analyzeLaunchedTestApp(socket, appArgs1, null, EXIT_RESTART); + appArgs1.remove(appArgs1.indexOf(TestLauncherConstants.EXITDATA_PARAMETER) + 1); + + for (int i = 0; i < 10; i++) { + socket = server.accept(); + + List appArgs2 = new ArrayList<>(); + analyzeLaunchedTestApp(socket, appArgs2, null, i == 9 ? EXIT_OK : EXIT_RESTART); + // Other than exitdata arg, all other args should be same over restarts + appArgs2.remove(appArgs2.indexOf(TestLauncherConstants.EXITDATA_PARAMETER) + 1); + + // Convert backslashes to forward slashes before comparison so that all paths are consistent + assertEquals(appArgs1.stream().map(s -> s.replace('\\', '/')).collect(Collectors.toList()), + appArgs2.stream().map(s -> s.replace('\\', '/')).collect(Collectors.toList())); + } + } + + @Test + void test_ArgsRemainSameOverRelaunches() throws IOException { + writeEclipseIni(DEFAULT_ECLIPSE_INI_CONTENT); + + startEclipseLauncher(List.of("-data", "dir1")); + + Socket socket = server.accept(); + + List appArgs1 = new ArrayList<>(); + analyzeLaunchedTestApp(socket, appArgs1, "--launcher.skipOldUserArgs\n-data\ndir1", EXIT_RELAUNCH); + appArgs1.remove(appArgs1.indexOf(TestLauncherConstants.EXITDATA_PARAMETER) + 1); + + for (int i = 0; i < 10; i++) { + socket = server.accept(); + + List appArgs2 = new ArrayList<>(); + analyzeLaunchedTestApp(socket, appArgs2, "--launcher.skipOldUserArgs\n-data\ndir1", i == 9 ? EXIT_OK : EXIT_RELAUNCH); + // Other than exitdata arg, all other args should be same over these relaunches + appArgs2.remove(appArgs2.indexOf(TestLauncherConstants.EXITDATA_PARAMETER) + 1); + + // Convert backslashes to forward slashes before comparison so that all paths are consistent + assertEquals(appArgs1.stream().map(s -> s.replace('\\', '/')).collect(Collectors.toList()), + appArgs2.stream().map(s -> s.replace('\\', '/')).collect(Collectors.toList())); + } + } + + private void analyzeLaunchedTestApp(Socket socket, List appArgs, String restartArgs, int appExitCode) + throws IOException { + BufferedReader in = new BufferedReader(new InputStreamReader(socket.getInputStream())); + DataOutputStream out = new DataOutputStream(socket.getOutputStream()); + out.writeBytes(TestLauncherConstants.ARGS_PARAMETER + "\n"); + out.flush(); + String line = null; + System.out.println("--- start ----"); + while ((line = in.readLine()) != null) { + if (TestLauncherConstants.MULTILINE_ARG_VALUE_TERMINATOR.equals(line)) + break; + System.out.println(line); + appArgs.add(line); + } + System.out.println("--- end ----"); + { + out.writeBytes(TestLauncherConstants.EXITDATA_PARAMETER + "\n"); + if (restartArgs != null && !restartArgs.isBlank()) + out.writeBytes(restartArgs + "\n"); + out.writeBytes(TestLauncherConstants.MULTILINE_ARG_VALUE_TERMINATOR + "\n"); + out.flush(); + + out.writeBytes(TestLauncherConstants.EXITCODE_PARAMETER + "\n"); + out.writeBytes(appExitCode + "\n"); + out.flush(); + } + } + + private Process startEclipseLauncher(List args) throws IOException { + String launcherPath = new File( + "eclipse" + (System.getProperty("os.name").toLowerCase().contains("win") ? ".exe" : "")) + .getAbsolutePath(); + List allArgs = new ArrayList<>(); + allArgs.add(launcherPath); + allArgs.addAll(args); + ProcessBuilder pb = new ProcessBuilder(allArgs); + pb.environment().put(TestLauncherConstants.PORT_ENV_KEY, Integer.toString(server.getLocalPort())); + return pb.start(); + } + + private void writeEclipseIni(String content) throws IOException { + File iniFile = new File(ECLIPSE_INI_FILE_NAME); + iniFile.createNewFile(); + FileWriter myWriter = new FileWriter(ECLIPSE_INI_FILE_NAME); + myWriter.write(content); + myWriter.close(); + } + +} diff --git a/features/org.eclipse.equinox.executable.feature/library/win32/make_win64.mak b/features/org.eclipse.equinox.executable.feature/library/win32/make_win64.mak index f2b72b0232e..67b45457054 100644 --- a/features/org.eclipse.equinox.executable.feature/library/win32/make_win64.mak +++ b/features/org.eclipse.equinox.executable.feature/library/win32/make_win64.mak @@ -63,6 +63,7 @@ wcflags = -DUNICODE -I.. -DDEFAULT_OS="\"$(DEFAULT_OS)\"" \ -DDEFAULT_WS="\"$(DEFAULT_WS)\"" \ -I"$(JAVA_HOME)\include" -I"$(JAVA_HOME)\include\win32" \ $(cflags) +LIBRARY_FRAGMENT_NAME = org.eclipse.equinox.launcher.$(DEFAULT_WS).$(DEFAULT_OS).$(DEFAULT_OS_ARCH) all: $(EXEC) $(DLL) $(CONSOLE) eclipseMain.obj: ../eclipseUnicode.h ../eclipseCommon.h ../eclipseMain.c @@ -113,3 +114,31 @@ install: all clean: del $(EXEC) $(DLL) $(MAIN_OBJS) $(MAIN_CONSOLE_OBJS) $(DLL_OBJS) $(COMMON_OBJS) $(RES) + +# Convienience method to install produced output into a developer's eclipse for testing/development. +dev_build_install: all +!ifdef DEV_ECLIPSE + @echo Copying $(EXEC) and $(DLL) into your development eclipse folder + mkdir $(DEV_ECLIPSE) + copy $(EXEC) $(DEV_ECLIPSE) + mkdir $(DEV_ECLIPSE)\plugins + mkdir $(DEV_ECLIPSE)\plugins\$(LIBRARY_FRAGMENT_NAME) + copy $(DLL) $(DEV_ECLIPSE)\plugins\$(LIBRARY_FRAGMENT_NAME) +!else + !error $(DEV_INSTALL_ERROR_MSG) +!endif + +test: + mvn -f ../org.eclipse.launcher.tests/pom.xml clean verify -Dmaven.test.skip=true + nmake -f make_win64.mak dev_build_install LIBRARY_FRAGMENT_NAME=org.eclipse.equinox.launcher DEV_ECLIPSE=..\org.eclipse.launcher.tests\target\test-run + mvn -f ../org.eclipse.launcher.tests/pom.xml test + +DEV_INSTALL_ERROR_MSG =\ +Note:\ + DEV_ECLIPSE environmental variable is not defined.\ + You can download an integration build eclipse for testing and set DEV_ECLIPSE to point to it's folder\ + as per output of 'pwd'. Note, without trailing forwardslash. Integration build can be downloaded here:\ + See: https://download.eclipse.org/eclipse/downloads/\ + That way you can automatically build and copy eclipse and eclipse_XXXX.so into the relevant folders for testing. \ + E.g: you can put something like the following into your .bashrc\ + export DEV_ECLIPSE="/home/YOUR_USER/Downloads/eclipse-SDK-I20YYMMDD-XXXX-linux-win32-x86_64/eclipse"