blob: ed4bb7d13cd4b1bde02723edc9b6b061c6db58cc [file] [log] [blame]
Raphael97f3e042011-11-11 18:08:58 -08001/*
2 * Copyright (C) 2011 The Android Open Source Project
3 *
4 * Licensed under the Apache License, Version 2.0 (the "License");
5 * you may not use this file except in compliance with the License.
6 * You may obtain a copy of the License at
7 *
8 * http://www.apache.org/licenses/LICENSE-2.0
9 *
10 * Unless required by applicable law or agreed to in writing, software
11 * distributed under the License is distributed on an "AS IS" BASIS,
12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
13 * See the License for the specific language governing permissions and
14 * limitations under the License.
15 */
16
17/*
18 * "win_android.exe", for Windows only. Replaces the previous android.bat.
19 * In the final SDK, this is what becomes tools\android.exe and it runs
20 * the UI for the SDK Manager or the AVD Manager.
21 *
22 * Implementation details:
23 * - We don't have access to ATL or MFC.
24 * - We don't want to pull in things like STL.
25 * - No Unicode/MBCS support for now.
26 */
27
28#ifdef _WIN32
29
30#define _CRT_SECURE_NO_WARNINGS 1
31
32#include <direct.h>
33#include <stdio.h>
34#include <stdarg.h>
35#include <string.h>
36#include <windows.h>
37
38// VS vs MINGW specific includes
39#ifdef USE_MINGW
40 #define _ASSERT(x) // undef
41#else
42 #include <crtdbg.h> // for _ASSERT
43#endif
44
45// A NULL-terminated list of directory to create in the temp folder.
46static const char * sMkDirList[] = {
47 "lib",
48 "lib\\x86",
49 "lib\\x86_64",
50 NULL,
51};
52
53// A NULL-terminated list of file patterns to copy in the temp folder.
54// The folders must be listed in sMkDirList
55static const char * sFilesToCopy[] = {
56 "lib\\x86\\swt.jar",
57 "lib\\x86_64\\swt.jar",
58 "lib\\androidprefs.jar",
59 "lib\\org.eclipse.*",
60 "lib\\sdk*",
61 "lib\\common.jar",
62 "lib\\commons-compress*",
63 "lib\\swtmenubar.jar",
64 "lib\\commons-logging*",
65 "lib\\commons-codec*",
66 "lib\\httpclient*",
67 "lib\\httpcore*",
68 "lib\\httpmime*",
69 NULL,
70};
71
72static bool gDebug = false;
73
74
75// An array that knows its own size. Not dynamically resizable.
76template <class T> class CArray {
77 T* mPtr;
78 int mSize;
79public:
80 CArray(int size) {
81 mSize = size;
82 mPtr = new T[size];
83 }
84
85 ~CArray() {
86 if (mPtr != NULL) {
87 delete[] mPtr;
88 mPtr = NULL;
89 }
90 mSize = 0;
91 }
92
93 T& operator[](int i) {
94 _ASSERT(i >= 0 && i < mSize);
95 return mPtr[i];
96 }
97
98 int size() {
99 return mSize;
100 }
101};
102
103// A simple string class wrapper.
104class CString {
105protected:
106 char *mStr;
107public:
108 CString() { mStr = NULL; }
109 CString(const CString &str) { mStr = str.mStr == NULL ? NULL : _strdup(str.mStr); }
110 CString(const char *str) { mStr = NULL; set(str); }
111 CString(const char *start, int length) { mStr = NULL; set(start, length); }
112
113 CString& set(const char *str) {
114 _free();
115 if (str != NULL) {
116 mStr = _strdup(str);
117 }
118 return *this;
119 }
120
121 CString& set(const char *start, int length) {
122 _free();
123 if (start != NULL) {
124 mStr = (char *)malloc(length + 1);
125 strncpy(mStr, start, length);
126 mStr[length] = 0;
127 }
128 return *this;
129 }
130
131 CString& setv(const char *str, va_list ap) {
132 _free();
133 // _vscprintf(str, ap) is only available with the MSVCRT, not MinGW.
134 // Instead we'll iterate till we have enough space to generate the string.
135 int len = strlen(str) + 1024;
136 mStr = (char *)malloc(len);
137 strcpy(mStr, str); // provide a default in case vsnprintf totally fails
138 for (int guard = 0; guard < 10; guard++) {
139 int ret = vsnprintf(mStr, len, str, ap);
140 if (ret == -1) {
141 // Some implementations don't give the proper size needed
142 // so double the space and try again.
143 len *= 2;
144 } else if (ret >= len) {
145 len = ret + 1;
146 } else {
147 // There was enough space to write.
148 break;
149 }
150 mStr = (char *)realloc((void *)mStr, len);
151 strcpy(mStr, str); // provide a default in case vsnprintf totally fails
152 }
153 return *this;
154 }
155
156 CString& setf(const char *str, ...) {
157 _free();
158 va_list ap;
159 va_start(ap, str);
160 setv(str, ap);
161 va_end(ap);
162 return *this;
163 }
164
165 virtual ~CString() { _free(); }
166
167 // Returns the C string owned by this CString. It will be
168 // invalid as soon as this CString is deleted or out of scope.
169 operator const char* () {
170 return mStr;
171 }
172
173 bool isEmpty() {
174 return mStr == NULL || *mStr == 0;
175 }
176
177 int length() {
178 return mStr == NULL ? 0 : strlen(mStr);
179 }
180
181 CString& add(const char *s) {
182 if (mStr == NULL) {
183 set(s);
184 } else {
185 mStr = (char *)realloc((void *)mStr, strlen(mStr) + strlen(s) + 1);
186 strcat((char *)mStr, s);
187 }
188 return *this;
189 }
190
191 CArray<CString> * split(char sep) {
192 if (mStr == NULL) {
193 return new CArray<CString>(0);
194 }
195 const char *last = NULL;
196 int n = 0;
197 for (const char *s = mStr; *s; s++) {
198 if (*s == sep && s != mStr && (last == NULL || s > last+1)) {
199 n++;
200 last = s;
201 }
202 }
203
204 CArray<CString> *result = new CArray<CString>(n);
205 last = NULL;
206 n = 0;
207 for (const char *s = mStr; *s; s++) {
208 if (*s == sep) {
209 if (s != mStr && (last == NULL || s > last+1)) {
210 const char *start = last ? last : mStr;
211 (*result)[n++].set(start, s-start);
212 }
213 last = s+1;
214 }
215 }
216
217 return result;
218 }
219
220private:
221 void _free() {
222 if (mStr != NULL) {
223 free((void *)mStr);
224 mStr = NULL;
225 }
226 }
227
228};
229
230// A simple path class wrapper.
231class CPath : public CString {
232public:
233 CPath() : CString() { }
234 CPath(const CPath &str) : CString(str) { }
235 CPath(const char *str) : CString(str) { }
236 CPath(const char *start, int length) : CString(start, length) { }
237
238 // Appends a path segment, adding a \ as necessary.
239 CPath& addPath(const char *s) {
240 int n = length();
241 if (n > 0 && mStr[n-1] != '\\') add("\\");
242 add(s);
243 return *this;
244 }
245
246 // Returns true if file exist and is not a directory.
247 // There's no garantee we have rights to access it.
248 bool fileExists() {
249 if (mStr == NULL) return false;
250 DWORD attribs = GetFileAttributesA(mStr);
251 return attribs != INVALID_FILE_ATTRIBUTES &&
252 !(attribs & FILE_ATTRIBUTE_DIRECTORY);
253 }
254
255 // Returns true if file exist and is a directory.
256 // There's no garantee we have rights to access it.
257 bool dirExists() {
258 if (mStr == NULL) return false;
259 DWORD attribs = GetFileAttributesA(mStr);
260 return attribs != INVALID_FILE_ATTRIBUTES &&
261 (attribs & FILE_ATTRIBUTE_DIRECTORY) != 0;
262 }
263
264 // Returns a copy of the directory portion of the path, if any
265 CPath dirName() {
266 CPath result;
267 if (mStr != NULL) {
268 char *pos = strrchr(mStr, '\\');
269 if (pos != NULL) {
270 result.set(mStr, pos - mStr);
271 }
272 }
273 return result;
274 }
275
276 // Returns a pointer to the baseName part of the path.
277 // It becomes invalid if the path changes.
278 const char *baseName() {
279 if (mStr != NULL) {
280 char *pos = strrchr(mStr, '\\');
281 if (pos != NULL) {
282 return pos + 1;
283 }
284 }
285 return NULL;
286 }
287
288 // If the path ends with the given searchName, replace it by the new name
289 void replaceName(const char *searchName, const char* newName) {
290 if (mStr == NULL) return;
291 int n = length();
292 int sn = strlen(searchName);
293 if (n < sn) return;
294 // if mStr ends with searchName
295 if (strcmp(mStr + n - sn, searchName) == 0) {
296 int sn2 = strlen(newName);
297 if (sn2 > sn) {
298 mStr = (char *)realloc((void *)mStr, n + sn2 - sn + 1);
299 }
300 strcpy(mStr + n - sn, newName);
301 mStr[n + sn2 - sn] = 0;
302 }
303 }
304};
305
306// ========= UTILITIES ==============
307
308// Displays a message in an ok+info dialog box.
309static void msgBox(const char* text, ...) {
310 CString formatted;
311 va_list ap;
312 va_start(ap, text);
313 formatted.setv(text, ap);
314 va_end(ap);
315
316 MessageBoxA(NULL, formatted, "Android SDK Manager", MB_OK | MB_ICONINFORMATION);
317}
318
319static void displayLastError(const char *description, ...) {
320 CString formatted;
321 va_list ap;
322 va_start(ap, description);
323 formatted.setv(description, ap);
324 va_end(ap);
325
326 DWORD err = GetLastError();
327 LPSTR errStr;
328 if (FormatMessageA(FORMAT_MESSAGE_ALLOCATE_BUFFER | /* dwFlags */
329 FORMAT_MESSAGE_FROM_SYSTEM,
330 NULL, /* lpSource */
331 err, /* dwMessageId */
332 0, /* dwLanguageId */
333 (LPSTR)&errStr, /* lpBuffer */
334 0, /* nSize */
335 NULL) != 0) { /* va_list args */
336 formatted.add("\r\n");
337 formatted.add(errStr);
338 MessageBox(NULL, formatted, "Android SDK Manager - Error", MB_OK | MB_ICONERROR);
339 LocalFree(errStr);
340 }
341}
342
343// Executes the command line. Does not wait for the program to finish.
344// The return code is from CreateProcess (0 means failure), not the running app.
345static int execNoWait(const char *app, const char *params, const char *workDir) {
346 STARTUPINFO startup;
347 PROCESS_INFORMATION pinfo;
348
349 ZeroMemory(&pinfo, sizeof(pinfo));
350
351 ZeroMemory(&startup, sizeof(startup));
352 startup.cb = sizeof(startup);
353 startup.dwFlags = STARTF_USESHOWWINDOW;
354 startup.wShowWindow = SW_HIDE|SW_MINIMIZE;
355
356 int ret = CreateProcessA(
357 (LPSTR) app, /* program path */
358 (LPSTR) params, /* command-line */
359 NULL, /* process handle is not inheritable */
360 NULL, /* thread handle is not inheritable */
361 TRUE, /* yes, inherit some handles */
362 CREATE_NO_WINDOW, /* we don't want a console */
363 NULL, /* use parent's environment block */
364 workDir, /* use parent's starting directory */
365 &startup, /* startup info, i.e. std handles */
366 &pinfo);
367
368 if (ret) {
369 CloseHandle(pinfo.hProcess);
370 CloseHandle(pinfo.hThread);
371 }
372
373 return ret;
374}
375
376
377// Executes command, waits for completion and returns exit code.
378// As indicated in MSDN for CreateProcess, callers should double-quote the program name
379// e.g. cmd="\"c:\program files\myapp.exe\" arg1 arg2";
380static int execWait(const char *cmd) {
381 STARTUPINFO startup;
382 PROCESS_INFORMATION pinfo;
383
384 ZeroMemory(&pinfo, sizeof(pinfo));
385
386 ZeroMemory(&startup, sizeof(startup));
387 startup.cb = sizeof(startup);
388 startup.dwFlags = STARTF_USESHOWWINDOW;
389 startup.wShowWindow = SW_HIDE|SW_MINIMIZE;
390
391 int ret = CreateProcessA(
392 NULL, /* program path */
393 (LPSTR) cmd, /* command-line */
394 NULL, /* process handle is not inheritable */
395 NULL, /* thread handle is not inheritable */
396 TRUE, /* yes, inherit some handles */
397 CREATE_NO_WINDOW, /* we don't want a console */
398 NULL, /* use parent's environment block */
399 NULL, /* use parent's starting directory */
400 &startup, /* startup info, i.e. std handles */
401 &pinfo);
402
403 int result = -1;
404 if (ret) {
405 WaitForSingleObject(pinfo.hProcess, INFINITE);
406
407 DWORD exitCode;
408 if (GetExitCodeProcess(pinfo.hProcess, &exitCode)) {
409 // this should not return STILL_ACTIVE (259)
410 result = exitCode;
411 }
412 CloseHandle(pinfo.hProcess);
413 CloseHandle(pinfo.hThread);
414 }
415
416 return result;
417}
418
419static bool getModuleDir(CPath *outDir) {
420 CHAR programDir[MAX_PATH];
421 int ret = GetModuleFileName(NULL, programDir, sizeof(programDir));
422 if (ret != 0) {
423 // Remove the last segment to keep only the directory.
424 int pos = ret - 1;
425 while (pos > 0 && programDir[pos] != '\\') {
426 --pos;
427 }
428 outDir->set(programDir, pos);
429 return true;
430 }
431 return false;
432}
433
434// Disable the FS redirection done by WOW64.
435// Because this runs as a 32-bit app, Windows automagically remaps some
436// folder under the hood (e.g. "Programs Files(x86)" is mapped as "Program Files").
437// This prevents the app from correctly searching for java.exe in these folders.
438// The registry is also remapped.
439static PVOID disableWow64FsRedirection() {
440
441 // The call we want to make is the following:
442 // PVOID oldWow64Value;
443 // Wow64DisableWow64FsRedirection(&oldWow64Value);
444 // However that method may not exist (e.g. on non-64 systems) so
445 // we must not call it directly.
446
447 PVOID oldWow64Value = 0;
448
449 HMODULE hmod = LoadLibrary("kernel32.dll");
450 if (hmod != NULL) {
451 FARPROC proc = GetProcAddress(hmod, "Wow64DisableWow64FsRedirection");
452 if (proc != NULL) {
453 typedef BOOL (WINAPI *disableWow64FuncType)(PVOID *);
454 disableWow64FuncType funcPtr = (disableWow64FuncType)proc;
455 funcPtr(&oldWow64Value);
456 }
457
458 FreeLibrary(hmod);
459 }
460
461 return oldWow64Value;
462}
463
464//Reverts the redirection disabled in disableWow64FsRedirection.
465static void revertWow64FsRedirection(PVOID oldWow64Value) {
466
467 // The call we want to make is the following:
468 // Wow64RevertWow64FsRedirection(oldWow64Value);
469 // However that method may not exist (e.g. on non-64 systems) so
470 // we must not call it directly.
471
472 HMODULE hmod = LoadLibrary("kernel32.dll");
473 if (hmod != NULL) {
474 FARPROC proc = GetProcAddress(hmod, "Wow64RevertWow64FsRedirection");
475 if (proc != NULL) {
476 typedef BOOL (WINAPI *revertWow64FuncType)(PVOID);
477 revertWow64FuncType funcPtr = (revertWow64FuncType)proc;
478 funcPtr(oldWow64Value);
479 }
480
481 FreeLibrary(hmod);
482 }
483}
484
485
486// =============================
487
488// Search java.exe in the path
489static bool findJavaInEnvPath(CPath *outJavaPath) {
490 SetLastError(0);
491 const char* envPath = getenv("PATH");
492 if (!envPath) return false;
493
494 CArray<CString> *paths = CString(envPath).split(';');
495 for(int i = 0; i < paths->size(); i++) {
496 CPath p((*paths)[i]);
497 p.addPath("java.exe");
498 if (p.fileExists()) {
499 // Make sure we can actually run "java -version".
500 CString cmd;
501 cmd.setf("\"%s\" -version", (const char*)p);
502 int code = execWait(cmd);
503 if (code == 0) {
504 if (gDebug) msgBox("Java found via env path: %s", (const char *)p);
505 outJavaPath->set(p);
506 delete paths;
507 return true;
508 }
509 }
510 }
511
512 delete paths;
513 return false;
514}
515
516static bool findJavaInRegistry(CPath *outJavaPath) {
517 // TODO
518 return false;
519}
520
521static bool findJavaInProgramFiles(CPath *outJavaPath) {
522 // TODO
523 return false;
524}
525
526// Creates a directory named dirLeafName in the TEMP directory.
527// Returns the path in outDir on success.
528static bool mkTempDir(const char *dirLeafName, CPath *outDir) {
529 SetLastError(0);
530 char tempPath[MAX_PATH + 1] = "";
531 DWORD len = GetTempPathA(MAX_PATH, tempPath);
532 if (len > 0 && len <= MAX_PATH) {
533 _ASSERT(tempPath[len-1] == '\\');
534 _ASSERT(len + strlen(dirLeafName) < MAX_PATH);
535 if (len + strlen(dirLeafName) >= MAX_PATH) {
536 displayLastError("TEMP path too long to create a temporary directory: %s", tempPath);
537 return false;
538 }
539 strcat(tempPath, dirLeafName);
540 outDir->set(tempPath);
541
542 if (outDir->dirExists() ||
543 CreateDirectoryA(tempPath, NULL /*lpSecurityAttributes*/) != 0) {
544 return true;
545 }
546 }
547 displayLastError("Failed to create a temporary directory: %s", tempPath);
548 return false;
549}
550
551// Creates all the directories from sMkDirList in the specified base tmpDir.
552static bool mkDirs(const char *tmpDir, const char * dirList[]) {
553 SetLastError(0);
554 for (const char **dir = dirList; *dir != NULL; dir++) {
555 CPath path(tmpDir);
556 path.addPath(*dir);
557 if (!path.dirExists()) {
558 if (!CreateDirectoryA(path, NULL /*lpSecurityAttributes*/)) {
559 displayLastError("Failed to create directory: %s", (const char *)path);
560 return false;
561 }
562 }
563 }
564 return true;
565}
566
567static bool copyFiles(const char *toolsDir, const char *tmpDir, const char *globList[]) {
568 SetLastError(0);
569 WIN32_FIND_DATAA srcFindData;
570 WIN32_FIND_DATAA destFindData;
571 for (const char **glob = globList; *glob != NULL; glob++) {
572 CPath globDir = CPath(*glob).dirName();
573
574 CPath fullGlob(toolsDir);
575 fullGlob.addPath(*glob);
576
577 HANDLE srcH = FindFirstFileA(fullGlob, &srcFindData);
578 if (srcH == INVALID_HANDLE_VALUE) {
579 displayLastError("Failed to list files: %s", *glob);
580 return false;
581 }
582 do {
583 // Skip directories
584 if ((srcFindData.dwFileAttributes & FILE_ATTRIBUTE_DIRECTORY) != 0) {
585 continue;
586 }
587 CPath srcPath(toolsDir);
588 srcPath.addPath(globDir).addPath(srcFindData.cFileName);
589
590 CPath destPath(tmpDir);
591 destPath.addPath(globDir).addPath(srcFindData.cFileName);
592
593 // Skip copy if files are likely to not have changed.
594 HANDLE destH = FindFirstFileA(destPath, &destFindData);
595 if (destH != INVALID_HANDLE_VALUE) {
596 // Size must be same for us to skip it.
597 if (srcFindData.nFileSizeHigh == destFindData.nFileSizeHigh &&
598 srcFindData.nFileSizeLow == destFindData.nFileSizeLow) {
599 // Creation & access times can differ. However if the dest write time
600 // is >= than the source write time, it should be the same file.
601 LARGE_INTEGER srcWriteTime;
602 LARGE_INTEGER dstWriteTime;
603 srcWriteTime.HighPart = srcFindData.ftLastWriteTime.dwHighDateTime;
604 srcWriteTime.LowPart = srcFindData.ftLastWriteTime.dwLowDateTime;
605 dstWriteTime.HighPart = destFindData.ftLastWriteTime.dwHighDateTime;
606 dstWriteTime.LowPart = destFindData.ftLastWriteTime.dwLowDateTime;
607 if (dstWriteTime.QuadPart >= srcWriteTime.QuadPart) {
608 FindClose(destH);
609 continue;
610 }
611 }
612
613 FindClose(destH);
614
615 // CopyFile copies some attributes. It's common for tools to be unzipped
616 // as read-only so we need to remove any r-o attribute on existing
617 // files if we want a recopy to succeed.
618 if ((destFindData.dwFileAttributes && FILE_ATTRIBUTE_READONLY) != 0) {
619 SetFileAttributes(destPath,
620 destFindData.dwFileAttributes ^ FILE_ATTRIBUTE_READONLY);
621 }
622 }
623
624 if (!CopyFileA(srcPath, destPath, false /*bFailIfExists*/)) {
625 FindClose(srcH);
626 displayLastError("Failed to copy file: %s", (const char*)destPath);
627 return false;
628 }
629 } while (FindNextFileA(srcH, &srcFindData) != 0);
630 FindClose(srcH);
631 }
632 return true;
633}
634
635static bool execSdkManager(const char *javaPath,
636 const char *toolsDir,
637 const char *tmpDir,
638 const char *lpCmdLine) {
639 SetLastError(0);
640
641 CPath javawPath(javaPath);
642 javawPath.replaceName("java.exe", "javaw.exe");
643 // Only accept it if we can actually find the exec
644 PVOID oldWow64Value = disableWow64FsRedirection();
645 if (!javawPath.fileExists()) {
646 javawPath.set(javaPath);
647 }
648 revertWow64FsRedirection(&oldWow64Value);
649
650 // Check whether the underlying system is x86 or x86_64.
651 // We use GetSystemInfo which will see the one masqueraded by Wow64.
652 // (to get the real info, we would use GetNativeSystemInfo instead.)
653 SYSTEM_INFO sysInfo;
654 GetSystemInfo(&sysInfo);
655
656 CString arch("x86");
657 if (sysInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_AMD64) {
658 arch.set("x86_64");
659 } else if (sysInfo.wProcessorArchitecture == PROCESSOR_ARCHITECTURE_INTEL) {
660 // Skip this. We'll just assume x86 and let it fail later.
661 // displayLastError("Unknown Processor Architecture: %d", sysInfo.wProcessorArchitecture);
662 // return false;
663 }
664
665 // Now build the command line.
666 // Note that we pass the absolute javawPath to execNoWait
667 // and the first parameter is just for the show.
668 // Important: for the classpath to be able to contain "lib\\sdkmanager.jar", etc.,
669 // we need to set the toolsDir as the *temp* directory in execNoWait.
670 // It's important to not use toolsDir otherwise it would lock that diretory.
671
672 // Tip: to connect the Java debugging to a running process, add this to the Java command line:
673 // "-Xdebug -Xrunjdwp:transport=dt_socket,server=y,suspend=y,address=8000"
674
675 CString cmdLine;
676 cmdLine.setf("\"%s\" " // javaPath
677 "-Dcom.android.sdkmanager.toolsdir=\"%s\" " // toolsDir
678 "-Dcom.android.sdkmanager.workdir=\"%s\" " // workDir==toolsdir
679 "-classpath \"lib\\sdkmanager.jar;lib\\swtmenubar.jar;lib\\%s\\swt.jar\" " // arch
680 "com.android.sdkmanager.Main "
681 "%s", // extra parameters
682 javawPath.baseName(), toolsDir, tmpDir, (const char *)arch, lpCmdLine);
683
684 if (gDebug) msgBox("Executing: %s", (const char *)cmdLine);
685
686 if (!execNoWait(javawPath, cmdLine, tmpDir)) {
687 displayLastError("Failed to run %s", (const char *)cmdLine);
688 return false;
689 }
690
691 return true;
692}
693
694int APIENTRY WinMain(HINSTANCE hInstance,
695 HINSTANCE hPrevInstance,
696 LPTSTR lpCmdLine,
697 int nCmdShow) {
698
699 gDebug = (getenv("ANDROID_SDKMAN_DEBUG") != NULL);
700
701 PVOID oldWow64Value = disableWow64FsRedirection();
702
703 CPath javaPath;
704 if (!findJavaInEnvPath(&javaPath) &&
705 !findJavaInRegistry(&javaPath) &&
706 !findJavaInProgramFiles(&javaPath)) {
707 msgBox("Failed to find Java on your system. Please reinstall it.");
708 return 2;
709 }
710 _ASSERT(!javaPath.isEmpty());
711
712 revertWow64FsRedirection(oldWow64Value);
713
714 // For debugging it's more convenient to be able to override the tools directory location
715 CPath toolsDir(getenv("ANDROID_SDKMAN_TOOLS_DIR"));
716 if (toolsDir.isEmpty()) {
717 if (!getModuleDir(&toolsDir)) {
718 displayLastError("Failed to get program's filename: ");
719 return 1;
720 }
721 }
722 _ASSERT(!toolsDir.isEmpty());
723
724 CPath tmpDir;
725 if (!mkTempDir("temp-android-tool", &tmpDir)) {
726 return 1;
727 }
728 _ASSERT(!tmpDir.isEmpty());
729
730 if (!mkDirs(tmpDir, sMkDirList)) {
731 return 1;
732 }
733
734 if (!copyFiles(toolsDir, tmpDir, sFilesToCopy)) {
735 return 1;
736 }
737
738 if (!execSdkManager(javaPath, toolsDir, tmpDir, lpCmdLine)) {
739 displayLastError("Failed to start SDK Manager: ");
740 return 1;
741 }
742
743 return 0;
744}
745#endif /* _WIN32 */