From c06e3ffebd7bf1aad81d958a644ed8a5871bb997 Mon Sep 17 00:00:00 2001 From: Umair Sair Date: Wed, 15 Nov 2023 07:45:06 +0500 Subject: [PATCH] Restarting workspace should take into account changes to eclipse.ini Background: =========== There are 3 return codes supported by Eclipse - 0 - Normal exit - 23 - Restart the eclipse with previous launcher arguments - 24 - Restart the eclipse with the arguments provided by Eclipse application itself The above mentioned problem is not with the exit code 23. The reason is that the eclipse launcher restarts itself in this case, hence eclipse.ini is also reloaded. However eclipse launcher does not restart itself in case of code 24 and parses the arguments provided by Eclipse application in shared memory and simply relaunches java, which means that eclipse.ini will not be reloaded. For restarts, eclipse should be using code 23 but we had to switch to code 24 to fix following issue. [[WorkbenchLauncher] Restart asks for the workspace to run](https://bugs.eclipse.org/bugs/show_bug.cgi?id=264072) And then the fix of following built up on it. [[Workspace chooser] "Older Workspace Version" dialog: "Cancel" button shouldn't close Eclipse but go back to workspace chooser](https://bugs.eclipse.org/bugs/show_bug.cgi?id=538830) Fix: ==== For code 24, the arguments in shared memory are for launcher, so instead of launcher just parsing the args and relaunching java, it should restart the launcher with appropriate arguments. With relaunching the launcher, eclipse.ini will be reloaded like its done for code 23. Eclipse launcher is updated to relaunch for code 24 as well. Moreover, there are 3 new launcher arguments introduced, 2 are internal to launcher and one is external to launcher and can be set by eclipse application as a relaunch argument. - `--launcher.oldUserArgsStart` and `--launcher.oldUserArgsEnd`: The user arguments passed to eclipse launcher for first start are tracked between these arguments during relaunches. E.g., if eclipse was started as `eclipse A B C` where A B C are some arguments to launcher and for relaunch, Eclipse application mentioned `X Y Z` arguments then relaunch command will be `eclipse --launcher.oldUserArgsStart A B C --launcher.oldUserArgsEnd A B C X Y Z`. Moreover, launcher relaunch will ignore all arguments between and including `--launcher.oldUserArgsStart` and `--launcher.oldUserArgsEnd`. - `--launcher.skipOldUserArgs`: If eclipse application wants to relaunch with provided arguments only and ignore the older user args then it can mention this argument. So for the case where eclipse was started as `eclipse A B C` and Eclipse application mentioned `X Y Z` as relaunch arguments along with `--launcher.skipOldUserArgs` then relaunch command will be `eclipse --launcher.oldUserArgsStart A B C --launcher.oldUserArgsEnd X Y Z` where launcher relaunch will ignore all arguments between and including `--launcher.oldUserArgsStart` and `--launcher.oldUserArgsEnd`. For restarts, Eclipse application just mentions the `-data` argument now as relaunch arguments instead of mentioning complete java args. However, user can still mention more arguments for relaunch and Eclipse restart will respect them as well and append the `-data` argument at the end of user arguments. --- .github/workflows/build.yml | 54 +- .../library/eclipse.c | 404 ++++++++------- .../library/eclipseCommon.h | 49 ++ .../library/eclipseMain.c | 49 +- .../library/gtk/build.sh | 4 +- .../library/gtk/make_linux.mak | 14 +- .../org.eclipse.launcher.tests/.classpath | 21 + .../org.eclipse.launcher.tests/.gitignore | 1 + .../org.eclipse.launcher.tests/.project | 23 + .../.settings/org.eclipse.jdt.core.prefs | 15 + .../.settings/org.eclipse.m2e.core.prefs | 4 + .../org.eclipse.launcher.tests/pom.xml | 58 +++ .../src/main/TestLauncherApp.java | 102 ++++ .../src/main/TestLauncherConstants.java | 23 + .../eclipse/equinox/launcher/JNIBridge.java | 1 + .../src/test/java/LauncherTests.java | 472 ++++++++++++++++++ .../library/win32/make_win64.mak | 28 ++ 17 files changed, 1108 insertions(+), 214 deletions(-) create mode 100644 features/org.eclipse.equinox.executable.feature/library/org.eclipse.launcher.tests/.classpath create mode 100644 features/org.eclipse.equinox.executable.feature/library/org.eclipse.launcher.tests/.gitignore create mode 100644 features/org.eclipse.equinox.executable.feature/library/org.eclipse.launcher.tests/.project create mode 100644 features/org.eclipse.equinox.executable.feature/library/org.eclipse.launcher.tests/.settings/org.eclipse.jdt.core.prefs create mode 100644 features/org.eclipse.equinox.executable.feature/library/org.eclipse.launcher.tests/.settings/org.eclipse.m2e.core.prefs create mode 100644 features/org.eclipse.equinox.executable.feature/library/org.eclipse.launcher.tests/pom.xml create mode 100644 features/org.eclipse.equinox.executable.feature/library/org.eclipse.launcher.tests/src/main/TestLauncherApp.java create mode 100644 features/org.eclipse.equinox.executable.feature/library/org.eclipse.launcher.tests/src/main/TestLauncherConstants.java create mode 120000 features/org.eclipse.equinox.executable.feature/library/org.eclipse.launcher.tests/src/org/eclipse/equinox/launcher/JNIBridge.java create mode 100644 features/org.eclipse.equinox.executable.feature/library/org.eclipse.launcher.tests/src/test/java/LauncherTests.java 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..2e39f5401e8 --- /dev/null +++ b/features/org.eclipse.equinox.executable.feature/library/org.eclipse.launcher.tests/src/main/TestLauncherApp.java @@ -0,0 +1,102 @@ +/******************************************************************************* + * 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 static void main(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"); + } + + System.exit(exitCode); + } + + 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..74ab4beeb53 --- /dev/null +++ b/features/org.eclipse.equinox.executable.feature/library/org.eclipse.launcher.tests/src/test/java/LauncherTests.java @@ -0,0 +1,472 @@ +/******************************************************************************* + * 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 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")); + + assertEquals(appArgs1, appArgs2); + } + + @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")); + + assertEquals(appArgs1, appArgs2); + } + + @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); + assertEquals(appArgs1, appArgs2); + } + } + + @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); + assertEquals(appArgs1, appArgs2); + } + } + + 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..e8e9ef7095d 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,30 @@ 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 + !message Copying $(EXEC) and $(DLL) into your development eclipse folder + mkdir $(DEV_ECLIPSE)/ + copy $(EXEC) $(DEV_ECLIPSE)/ + 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"