SDK Windows: more ways to find java.

Also properly redirect command-line output
to the parent shell console.

This is experimental. This CL removes "android.exe"
from the Windows tools and only keeps android.bat.
However in a next CL android.bat will be changed
to use 'find_java' to locate the best Java exe around
(this is currently done in a bat script)

Change-Id: I6e5485fdf59fde9838cf929ff333e1c611ea7bb4
diff --git a/sdkmanager/win_android/find_java.cpp b/sdkmanager/win_android/find_java.cpp
index a3b7af8..eea09ef 100755
--- a/sdkmanager/win_android/find_java.cpp
+++ b/sdkmanager/win_android/find_java.cpp
@@ -17,32 +17,59 @@
 #ifdef _WIN32

 

 #include "find_java.h"

-

-#define _CRT_SECURE_NO_WARNINGS 1

+#include <shlobj.h>

 

 extern bool gDebug;

 

-// Search java.exe in the path

+// Check whether we can find $PATH/java.exe

+static bool checkPath(CPath *inOutPath) {

+    inOutPath->addPath("java.exe");

+

+    bool result = false;

+    PVOID oldWow64Value = disableWow64FsRedirection();

+    if (inOutPath->fileExists()) {

+        // Make sure we can actually run "java -version".

+        CString cmd;

+        cmd.setf("\"%s\" -version", inOutPath->cstr());

+        int code = execWait(cmd.cstr());

+        result = (code == 0);

+    }

+

+    revertWow64FsRedirection(oldWow64Value);

+    return result;

+}

+

+// Check whether we can find $PATH/bin/java.exe

+static bool checkBinPath(CPath *inOutPath) {

+    inOutPath->addPath("bin");

+    return checkPath(inOutPath);

+}

+

+// Search java.exe in the environment

 bool findJavaInEnvPath(CPath *outJavaPath) {

     SetLastError(0);

-    const char* envPath = getenv("PATH");

+

+    const char* envPath = getenv("JAVA_HOME");

+    if (envPath != NULL) {

+        CPath p(envPath);

+        if (checkBinPath(&p)) {

+            if (gDebug) msgBox("Java found via JAVA_HOME: %s", p.cstr());

+            *outJavaPath = p;

+            return true;

+        }

+    }

+

+    envPath = getenv("PATH");

     if (!envPath) return false;

 

     CArray<CString> *paths = CString(envPath).split(';');

     for(int i = 0; i < paths->size(); i++) {

         CPath p((*paths)[i].cstr());

-        p.addPath("java.exe");

-        if (p.fileExists()) {

-            // Make sure we can actually run "java -version".

-            CString cmd;

-            cmd.setf("\"%s\" -version", p.cstr());

-            int code = execWait(cmd.cstr());

-            if (code == 0) {

-                if (gDebug) msgBox("Java found via env path: %s", p.cstr());

-                outJavaPath->set(p.cstr());

-                delete paths;

-                return true;

-            }

+        if (checkPath(&p)) {

+            if (gDebug) msgBox("Java found via env PATH: %s", p.cstr());

+            *outJavaPath = p;

+            delete paths;

+            return true;

         }

     }

 

@@ -50,14 +77,245 @@
     return false;

 }

 

-bool findJavaInRegistry(CPath *outJavaPath) {

-    // TODO

+// --------------

+

+bool getRegValue(const char *keyPath, const char *keyName, REGSAM access, CString *outValue) {

+    HKEY key;

+    LSTATUS status = RegOpenKeyExA(

+        HKEY_LOCAL_MACHINE,         // hKey

+        keyPath,                    // lpSubKey

+        0,                          // ulOptions

+        KEY_READ | access,          // samDesired,

+        &key);                      // phkResult

+    if (status == ERROR_SUCCESS) {

+

+        LSTATUS ret = ERROR_MORE_DATA;

+        DWORD size = 4096; // MAX_PATH is 260, so 4 KB should be good enough

+        char* buffer = (char*) malloc(size);

+

+        while (ret == ERROR_MORE_DATA && size < (1<<16) /*64 KB*/) {

+            ret = RegQueryValueExA(

+                key,                // hKey

+                keyName,            // lpValueName

+                NULL,               // lpReserved

+                NULL,               // lpType

+                (LPBYTE) buffer,    // lpData

+                &size);             // lpcbData

+

+            if (ret == ERROR_MORE_DATA) {

+                size *= 2;

+                buffer = (char*) realloc(buffer, size);

+            } else {

+                buffer[size] = 0;

+            }

+        }

+

+        if (ret != ERROR_MORE_DATA) outValue->set(buffer);

+

+        free(buffer);

+        RegCloseKey(key);

+

+        return (ret != ERROR_MORE_DATA);

+    }

+

     return false;

 }

 

-bool findJavaInProgramFiles(CPath *outJavaPath) {

-    // TODO

+bool exploreJavaRegistry(const char *entry, REGSAM access, CPath *outJavaPath) {

+

+    // Let's visit HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Java Runtime Environment [CurrentVersion]

+    CPath subKey("SOFTWARE\\JavaSoft\\");

+    subKey.addPath(entry);

+

+    CString currVersion;

+    if (getRegValue(subKey.cstr(), "CurrentVersion", access, &currVersion)) {

+        // CurrentVersion should be something like "1.7".

+        // We want to read HKEY_LOCAL_MACHINE\SOFTWARE\JavaSoft\Java Runtime Environment\1.7 [JavaHome]

+        subKey.addPath(currVersion);

+        CPath javaHome;

+        if (getRegValue(subKey.cstr(), "JavaHome", access, &javaHome)) {

+            if (checkBinPath(&javaHome)) {

+                *outJavaPath = javaHome;

+                return true;

+            }

+        }

+    }

+

     return false;

 }

 

+bool findJavaInRegistry(CPath *outJavaPath) {

+    // We'll do the registry test 3 times: first using the default mode,

+    // then forcing the use of the 32-bit registry then forcing the use of

+    // 64-bit registry. On Windows 2k, the 2 latter will fail since the

+    // flags are not supported. On a 32-bit OS the 64-bit is obviously

+    // useless and the 2 first test should be equivalent so we just

+    // need the first case.

+

+    // Check the JRE first, then the JDK.

+    if (exploreJavaRegistry("Java Runtime Environment", 0, outJavaPath) ||

+            exploreJavaRegistry("Java Development Kit", 0, outJavaPath)) {

+        return true;

+    }

+

+    // Check the real sysinfo state (not the one hidden by WOW64) for x86

+    SYSTEM_INFO sysInfo;

+    GetNativeSystemInfo(&sysInfo);

+

+    if (sysInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) {

+        if (exploreJavaRegistry("Java Runtime Environment", KEY_WOW64_32KEY, outJavaPath) ||

+                exploreJavaRegistry("Java Development Kit", KEY_WOW64_32KEY, outJavaPath)) {

+            return true;

+        }

+

+        if (exploreJavaRegistry("Java Runtime Environment", KEY_WOW64_64KEY, outJavaPath) ||

+                exploreJavaRegistry("Java Development Kit", KEY_WOW64_64KEY, outJavaPath)) {

+            return true;

+        }

+    }

+

+    return false;

+}

+

+// --------------

+

+static bool checkProgramFiles(CPath *outJavaPath) {

+

+    char programFilesPath[MAX_PATH + 1];

+    HRESULT result = SHGetFolderPathA(

+        NULL,                       // hwndOwner

+        CSIDL_PROGRAM_FILES,        // nFolder

+        NULL,                       // hToken

+        SHGFP_TYPE_CURRENT,         // dwFlags

+        programFilesPath);          // pszPath

+    if (FAILED(result)) return false;

+

+    CPath path(programFilesPath);

+    path.addPath("Java");

+

+    // Do we have a C:\\Program Files\\Java directory?

+    if (!path.dirExists()) return false;

+

+    CPath glob(path);

+    glob.addPath("j*");

+

+    bool found = false;

+    WIN32_FIND_DATAA findData;

+    HANDLE findH = FindFirstFileA(glob.cstr(), &findData);

+    if (findH == INVALID_HANDLE_VALUE) return false;

+    do {

+        if ((findData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {

+            CPath temp(path);

+            temp.addPath(findData.cFileName);

+            // Check C:\\Program Files[x86]\\Java\\{jdk,jre}*\\bin\\java.exe

+            if (checkBinPath(&temp)) {

+                found = true;

+                *outJavaPath = temp;

+            }

+        }

+    } while (!found && FindNextFileA(findH, &findData) != 0);

+    FindClose(findH);

+

+    return found;

+}

+

+bool findJavaInProgramFiles(CPath *outJavaPath) {

+    // Check the C:\\Program Files (x86) directory

+    // With WOW64 fs redirection in place by default, we should get the x86

+    // version on a 64-bit OS since this app is a 32-bit itself.

+    if (checkProgramFiles(outJavaPath)) return true;

+

+    // Check the real sysinfo state (not the one hidden by WOW64) for x86

+    SYSTEM_INFO sysInfo;

+    GetNativeSystemInfo(&sysInfo);

+

+    if (sysInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) {

+        // On a 64-bit OS, try again by disabling the fs redirection so

+        // that we can try the real C:\\Program Files directory.

+        PVOID oldWow64Value = disableWow64FsRedirection();

+        bool found = checkProgramFiles(outJavaPath);

+        revertWow64FsRedirection(oldWow64Value);

+        return found;

+    }

+

+    return false;

+}

+

+// --------------

+

+static bool getJavaVersion(CPath &javaPath, CString *version) {

+    bool result = false;

+

+    // Run "java -version".

+    // TODO: capture output to string.

+    CString cmd;

+    cmd.setf("\"%s\" -version", javaPath.cstr());

+

+    SECURITY_ATTRIBUTES saAttr;

+    STARTUPINFO           startup;

+    PROCESS_INFORMATION   pinfo;

+

+    // Want to inherit pipe handle

+    ZeroMemory(&saAttr, sizeof(saAttr));

+    saAttr.nLength = sizeof(SECURITY_ATTRIBUTES); 

+    saAttr.bInheritHandle = TRUE; 

+    saAttr.lpSecurityDescriptor = NULL; 

+

+    // Create pipe for stdout

+    HANDLE stdoutPipeRd, stdoutPipeWt;

+    if (!CreatePipe(

+            &stdoutPipeRd,      // hReadPipe,

+            &stdoutPipeWt,      // hWritePipe,

+            &saAttr,            // lpPipeAttributes,

+            0)) {               // nSize (0=default buffer size)

+        displayLastError("CreatePipe failed: ");

+        return false;

+    }

+

+

+    ZeroMemory(&pinfo, sizeof(pinfo));

+

+    ZeroMemory(&startup, sizeof(startup));

+    startup.cb          = sizeof(startup);

+    startup.dwFlags     = STARTF_USESHOWWINDOW;

+    startup.wShowWindow = SW_HIDE|SW_MINIMIZE;

+

+    int ret = CreateProcessA(

+            NULL,                                       /* program path */

+            (LPSTR) cmd,                                /* command-line */

+            NULL,                  /* process handle is not inheritable */

+            NULL,                   /* thread handle is not inheritable */

+            TRUE,                          /* yes, inherit some handles */

+            CREATE_NO_WINDOW,                /* we don't want a console */

+            NULL,                     /* use parent's environment block */

+            NULL,                    /* use parent's starting directory */

+            &startup,                 /* startup info, i.e. std handles */

+            &pinfo);

+

+    if (ret) {

+        WaitForSingleObject(pinfo.hProcess, INFINITE);

+

+        DWORD exitCode;

+        if (GetExitCodeProcess(pinfo.hProcess, &exitCode)) {

+            // this should not return STILL_ACTIVE (259)

+            result = exitCode == 0;

+        }

+        CloseHandle(pinfo.hProcess);

+        CloseHandle(pinfo.hThread);

+    }

+    CloseHandle(stdoutPipeRd);

+    CloseHandle(stdoutPipeWt);

+

+    if (result) {

+        // TODO

+        // Parse output of "java -version".

+        // It should be something like:

+        //   java version "1.6.0_29"

+        // (including the quotes.)

+    }

+

+    return result;

+}

+

+

 #endif /* _WIN32 */

diff --git a/sdkmanager/win_android/utils.h b/sdkmanager/win_android/utils.h
index 8c5cdb7..4530380 100755
--- a/sdkmanager/win_android/utils.h
+++ b/sdkmanager/win_android/utils.h
@@ -19,6 +19,8 @@
 

 #ifdef _WIN32

 

+#define _CRT_SECURE_NO_WARNINGS 1

+

 #include <direct.h>

 #include <stdio.h>

 #include <stdarg.h>

@@ -68,14 +70,20 @@
     char *mStr;

 public:

     CString()                              { mStr = NULL; }

-    CString(const CString &str)            { mStr = str.mStr == NULL ? NULL : _strdup(str.mStr); }

+    CString(const CString &str)            { mStr = NULL; set(str.mStr); }

     explicit CString(const char *str)      { mStr = NULL; set(str); }

     CString(const char *start, int length) { mStr = NULL; set(start, length); }

 

+    CString& operator=(const CString &str) {

+        return set(str.cstr());

+    }

+

     CString& set(const char *str) {

-        _free();

-        if (str != NULL) {

-            mStr = _strdup(str);

+        if (str != mStr) {

+            _free();

+            if (str != NULL) {

+                mStr = _strdup(str);

+            }

         }

         return *this;

     }

@@ -211,10 +219,16 @@
 class CPath : public CString {

 public:

     CPath()                              : CString()    { }

+    CPath(const CString &str)            : CString(str) { }

     CPath(const CPath &str)              : CString(str) { }

     explicit CPath(const char *str)      : CString(str) { }

     CPath(const char *start, int length) : CString(start, length) { }

 

+    CPath& operator=(const CPath &str) {

+        set(str.cstr());

+        return *this;

+    }

+

     // Appends a path segment, adding a \ as necessary.

     CPath& addPath(const CString &s) {

         return addPath(s.cstr());

diff --git a/sdkmanager/win_android/win_android.cpp b/sdkmanager/win_android/win_android.cpp
index 32d090c..3a768ec 100644
--- a/sdkmanager/win_android/win_android.cpp
+++ b/sdkmanager/win_android/win_android.cpp
@@ -29,7 +29,8 @@
 

 #include "utils.h"

 #include "find_java.h"

-

+#include <io.h>

+#include <fcntl.h>

 

 // A NULL-terminated list of directory to create in the temp folder.

 static const char * sMkDirList[] = {

@@ -178,6 +179,7 @@
     // The default is to use java.exe to automatically dump stdout in

     // the parent console.

     CPath javaExecPath(javaPath);

+    bool redirectStdout = true;

 

     // Attach to the parent console, if there's one.

     if (AttachConsole(-1) == 0) {

@@ -198,10 +200,16 @@
             PVOID oldWow64Value = disableWow64FsRedirection();

             if (!javaExecPath.fileExists()) {

                 javaExecPath.set(javaPath);

+                redirectStdout = false;

             }

             revertWow64FsRedirection(&oldWow64Value);

         }

     }

+    if (redirectStdout && GetStdHandle(STD_OUTPUT_HANDLE) != NULL) {

+        // If we have an output, redirect to it.

+        freopen("CONOUT$", "w", stdout);

+        freopen("CONOUT$", "w", stderr);

+    }

 

     // Check whether the underlying system is x86 or x86_64.

     // We use GetSystemInfo which will see the one masqueraded by Wow64.

@@ -226,7 +234,7 @@
     // It's important to not use toolsDir otherwise it would lock that diretory.

 

     CString cmdLine;

-    cmdLine.setf("\"%s\" "                                   // javaPath

+    cmdLine.setf("\"%s\" "                                   // javaExecPath basename

                  "-Dcom.android.sdkmanager.toolsdir=\"%s\" " // toolsDir

                  "-Dcom.android.sdkmanager.workdir=\"%s\" "  // workDir==toolsdir

                  "-classpath \"lib\\sdkmanager.jar;lib\\swtmenubar.jar;lib\\%s\\swt.jar\" " // arch

@@ -247,6 +255,69 @@
     return true;

 }

 

+// Attaches to a parent console or create one and then redirect stdout

+// and stderr to this console. Returns false if it failed to attach

+// or create the console.

+static bool sendStdoutToConsole() {

+    // See http://stackoverflow.com/questions/4028353 for some background.

+

+    HANDLE hConOut = GetStdHandle(STD_OUTPUT_HANDLE);

+    if (hConOut != NULL) {

+        // Std output is set. Might or might not be a console though.

+        // Try to attach to the parent console and use its ConOut.

+        if (AttachConsole(ATTACH_PARENT_PROCESS) != 0) {

+            goto redirect;

+        }

+    }

+

+    // There is no current console output.

+    // Create one and attach ConOut to stdout.

+    if (AllocConsole() == 0) {

+        displayLastError("AllocConsole failed: ");

+        return false;

+    }

+

+redirect:

+    // Redirect both stdout and stderr.

+    HANDLE hOut = GetStdHandle(STD_OUTPUT_HANDLE);

+    int fdOut = _open_osfhandle((intptr_t) hOut, _O_TEXT);

+    if (fdOut != -1) {

+        FILE *fpOut = _fdopen(fdOut, "w");

+        *stdout = *fpOut;

+    } else {

+        // Workaround for Cygwin when not redirecting to a pipe

+        freopen("CONOUT$", "w", stdout);

+    }

+

+    HANDLE hErr = GetStdHandle(STD_ERROR_HANDLE);

+    int fdErr = _open_osfhandle((intptr_t) hErr, _O_TEXT);

+    if (fdErr != -1) {

+        FILE *fpErr = _fdopen(fdErr, "w");

+        *stderr = *fpErr;

+    } else {

+        // Workaround for Cygwin when not redirecting to a pipe

+        // Note: there's is no such 'CONERR$'. See MSDN for GetStdHandle().

+        freopen("CONOUT$", "w", stderr);

+    }

+    return true;

+}

+

+static void testFindJava() {

+    sendStdoutToConsole();

+

+    CPath javaPath("<not found>");

+    bool ok = findJavaInEnvPath(&javaPath);

+    printf("findJavaInEnvPath: [%s] %s\n", ok ? "OK" : "FAIL", javaPath.cstr());

+

+    javaPath.set("<not found>");

+    ok = findJavaInRegistry(&javaPath);

+    printf("findJavaInRegistry [%s] %s\n", ok ? "OK" : "FAIL", javaPath.cstr());

+

+    javaPath.set("<not found>");

+    ok = findJavaInProgramFiles(&javaPath);

+    printf("findJavaInProgramFiles [%s] %s\n", ok ? "OK" : "FAIL", javaPath.cstr());

+}

+

 int APIENTRY WinMain(HINSTANCE hInstance,

                      HINSTANCE hPrevInstance,

                      LPTSTR    lpCmdLine,

@@ -254,7 +325,10 @@
 

     gDebug = (getenv("ANDROID_SDKMAN_DEBUG") != NULL);

 

-    PVOID oldWow64Value = disableWow64FsRedirection();

+    if (strcmp(lpCmdLine, "/test") == 0) {

+        testFindJava();

+        return 0;

+    }

 

     CPath javaPath;

     if (!findJavaInEnvPath(&javaPath) &&

@@ -265,8 +339,6 @@
     }

     _ASSERT(!javaPath.isEmpty());

 

-    revertWow64FsRedirection(oldWow64Value);

-

     // For debugging it's convenient to override the tools directory location

     CPath toolsDir(getenv("ANDROID_SDKMAN_TOOLS_DIR"));

     if (toolsDir.isEmpty()) {