Fix source mapping persistent file location
[qt-creator:android-qt-creator.git] / src / plugins / android / androidconfigurations.cpp
1 /*
2 I BogDan Vatra < bog_dan_ro@yahoo.com >, the copyright holder of this work,
3 hereby release it into the public domain. This applies worldwide.
4
5 In case this is not legally possible, I grant any entity the right to use
6 this work for any purpose, without any conditions, unless such conditions
7 are required by law.
8 */
9
10 #include "androidconfigurations.h"
11 #include "androidconstants.h"
12 #include "ui_addnewavddialog.h"
13
14 #include <coreplugin/icore.h>
15 #include <projectexplorer/persistentsettings.h>
16
17 #include <QtCore/QCoreApplication>
18 #include <QtCore/QDateTime>
19 #include <QtCore/QSettings>
20 #include <QtCore/QStringBuilder>
21 #include <QtCore/QStringList>
22 #include <QtCore/QProcess>
23 #include <QtCore/QFileInfo>
24 #include <QtCore/QDirIterator>
25 #include <QtCore/QMetaObject>
26
27 #include <QtGui/QStringListModel>
28 #include <QtGui/QMessageBox>
29 #include <QtGui/QMainWindow>
30
31 #if defined(_WIN32)
32 #include <iostream>
33 #include <windows.h>
34 #define sleep(_n) Sleep(1000 * (_n))
35 #endif
36
37 using namespace ProjectExplorer;
38
39 namespace Android {
40 namespace Internal {
41
42 namespace {
43     const QLatin1String SettingsGroup("AndroidConfigurations");
44     const QLatin1String SDKLocationKey("SDKLocation");
45     const QLatin1String NDKLocationKey("NDKLocation");
46     const QLatin1String NDKToolchainVersionKey("NDKToolchainVersion");
47     const QLatin1String AntLocationKey("AntLocation");
48     const QLatin1String ArmGdbLocationKey("GdbLocation");
49     const QLatin1String ArmGdbserverLocationKey("GdbserverLocation");
50     const QLatin1String X86GdbLocationKey("X86GdbLocation");
51     const QLatin1String X86GdbserverLocationKey("X86GdbserverLocation");
52     const QLatin1String OpenJDKLocationKey("OpenJDKLocation");
53     const QLatin1String KeystoreLocationKey("KeystoreLocation");
54     const QLatin1String PartitionSizeKey("PartitionSize");
55     const QLatin1String NDKGccVersionRegExp("\\d\\.\\d\\.\\d");
56     const QLatin1String ArmToolchainPrefix("arm-linux-androideabi");
57     const QLatin1String X86ToolchainPrefix("x86");
58     const QLatin1String ArmToolsPrefix("arm-linux-androideabi");
59     const QLatin1String X86ToolsPrefix("i686-android-linux");
60     const QLatin1String Unknown("unknown");
61     const QLatin1String keytoolName("keytool");
62     const QLatin1String jarsignerName("jarsigner");
63     const QLatin1String androidFilename("/android.xml");
64     const QLatin1String changeTimeStamp("ChangeTimeStamp");
65
66     static QString settingsFileName()
67     {
68         return Core::ICore::instance()->resourcePath()
69                 + QLatin1String("/Nokia") + androidFilename;
70     }
71
72     bool androidDevicesLessThan(const AndroidDevice &dev1, const AndroidDevice &dev2)
73     {
74         return dev1.sdk < dev2.sdk;
75     }
76 }
77
78 QLatin1String AndroidConfigurations::toolchainPrefix(ProjectExplorer::Abi::Architecture architecture)
79 {
80     switch (architecture) {
81     case ProjectExplorer::Abi::ArmArchitecture:
82         return ArmToolchainPrefix;
83     case ProjectExplorer::Abi::X86Architecture:
84         return X86ToolchainPrefix;
85     default:
86         return Unknown;
87     }
88 }
89
90
91 QLatin1String AndroidConfigurations::toolsPrefix(ProjectExplorer::Abi::Architecture architecture)
92 {
93     switch (architecture) {
94     case ProjectExplorer::Abi::ArmArchitecture:
95         return ArmToolsPrefix;
96     case ProjectExplorer::Abi::X86Architecture:
97         return X86ToolsPrefix;
98     default:
99         return Unknown;
100     }
101 }
102
103 AndroidConfig::AndroidConfig(const QSettings &settings)
104 {
105     // user settings
106     ArmGdbLocation = settings.value(ArmGdbLocationKey).toString();
107     ArmGdbserverLocation = settings.value(ArmGdbserverLocationKey).toString();
108     X86GdbLocation = settings.value(X86GdbLocationKey).toString();
109     X86GdbserverLocation = settings.value(X86GdbserverLocationKey).toString();
110     PartitionSize = settings.value(PartitionSizeKey, 1024).toInt();
111     SDKLocation = settings.value(SDKLocationKey).toString();
112     NDKLocation = settings.value(NDKLocationKey).toString();
113     AntLocation = settings.value(AntLocationKey).toString();
114     OpenJDKLocation = settings.value(OpenJDKLocationKey).toString();
115     KeystoreLocation = settings.value(KeystoreLocationKey).toString();
116
117     QRegExp versionRegExp(NDKGccVersionRegExp);
118     const QString & value=settings.value(NDKToolchainVersionKey).toString();
119     if (versionRegExp.exactMatch(value))
120         NDKToolchainVersion = value;
121     else
122         NDKToolchainVersion = value.mid(versionRegExp.indexIn(value));
123     // user settings
124
125     PersistentSettingsReader reader;
126     if (reader.load(settingsFileName()) && settings.value(changeTimeStamp).toInt() != QFileInfo(settingsFileName()).lastModified().toMSecsSinceEpoch()/1000)
127     {
128         // persisten settings
129         SDKLocation = reader.restoreValue(SDKLocationKey).toString();
130         NDKLocation = reader.restoreValue(NDKLocationKey).toString();
131         AntLocation = reader.restoreValue(AntLocationKey).toString();
132         OpenJDKLocation = reader.restoreValue(OpenJDKLocationKey).toString();
133         KeystoreLocation = reader.restoreValue(KeystoreLocationKey).toString();
134
135         QRegExp versionRegExp(NDKGccVersionRegExp);
136         const QString & value=reader.restoreValue(NDKToolchainVersionKey).toString();
137         if (versionRegExp.exactMatch(value))
138             NDKToolchainVersion = value;
139         else
140             NDKToolchainVersion = value.mid(versionRegExp.indexIn(value));
141
142         if (!ArmGdbLocation.length())
143             ArmGdbLocation = reader.restoreValue(ArmGdbLocationKey).toString();
144
145         if (!ArmGdbserverLocation.length())
146             ArmGdbserverLocation = reader.restoreValue(ArmGdbserverLocationKey).toString();
147
148         if (!X86GdbLocation.length())
149             X86GdbLocation = reader.restoreValue(X86GdbLocationKey).toString();
150
151         if (!X86GdbserverLocation.length())
152             X86GdbserverLocation = reader.restoreValue(X86GdbserverLocationKey).toString();
153         // persisten settings
154     }
155
156 }
157
158 AndroidConfig::AndroidConfig()
159 {
160     PartitionSize = 1024;
161 }
162
163 void AndroidConfig::save(QSettings &settings) const
164 {
165     QFileInfo fileInfo(settingsFileName());
166     if (fileInfo.exists())
167         settings.setValue(changeTimeStamp, fileInfo.lastModified().toMSecsSinceEpoch()/1000);
168
169     // user settings
170     settings.setValue(SDKLocationKey, SDKLocation);
171     settings.setValue(NDKLocationKey, NDKLocation);
172     settings.setValue(NDKToolchainVersionKey, NDKToolchainVersion);
173     settings.setValue(AntLocationKey, AntLocation);
174     settings.setValue(OpenJDKLocationKey, OpenJDKLocation);
175     settings.setValue(KeystoreLocationKey, KeystoreLocation);
176     settings.setValue(ArmGdbLocationKey, ArmGdbLocation);
177     settings.setValue(ArmGdbserverLocationKey, ArmGdbserverLocation);
178     settings.setValue(X86GdbLocationKey, X86GdbLocation);
179     settings.setValue(X86GdbserverLocationKey, X86GdbserverLocation);
180     settings.setValue(PartitionSizeKey, PartitionSize);
181     // user settings
182
183 }
184
185 void AndroidConfigurations::setConfig(const AndroidConfig &devConfigs)
186 {
187     m_config = devConfigs;
188     save();
189     updateAvailablePlatforms();
190     emit updated();
191 }
192
193 void AndroidConfigurations::updateAvailablePlatforms()
194 {
195     m_availablePlatforms.clear();
196     QDirIterator it(m_config.NDKLocation + "/platforms", QStringList() << "android-*", QDir::Dirs);
197     while (it.hasNext()) {
198         const QString &fileName = it.next();
199         m_availablePlatforms.push_back(fileName.mid(fileName.lastIndexOf('-') + 1).toInt());
200     }
201     qSort(m_availablePlatforms.begin(), m_availablePlatforms.end(), qGreater<int>());
202 }
203
204 QStringList AndroidConfigurations::sdkTargets(int minApiLevel)
205 {
206     QStringList targets;
207     QProcess proc;
208     proc.start(androidToolPath(), QStringList() << "list" << "target"); // list avaialbe AVDs
209     if (!proc.waitForFinished(-1)) {
210         proc.terminate();
211         return targets;
212     }
213     QList<QByteArray> avds = proc.readAll().trimmed().split('\n');
214     for (int i = 0; i<avds.size(); i++) {
215         QString line = avds[i];
216         int index = line.indexOf("\"android-");
217         if (index == -1)
218             continue;
219         QString apiLevel = line.mid(index + 1, line.length() - index - 2);
220         if (apiLevel.mid(apiLevel.lastIndexOf('-') + 1).toInt() >= minApiLevel)
221             targets.push_back(apiLevel);
222     }
223     return targets;
224 }
225
226 QStringList AndroidConfigurations::ndkToolchainVersions()
227 {
228     QRegExp versionRegExp(NDKGccVersionRegExp);
229     QStringList result;
230     QDirIterator it(m_config.NDKLocation+"/toolchains", QStringList() << "*", QDir::Dirs);
231     while (it.hasNext()) {
232         const QString &fileName = it.next();
233         int idx = versionRegExp.indexIn(fileName);
234         if (idx == -1)
235             continue;
236         QString version = fileName.mid(idx);
237         if (!result.contains(version))
238             result.append(version);
239     }
240     return result;
241 }
242
243 QString AndroidConfigurations::adbToolPath()
244 {
245     return m_config.SDKLocation+QLatin1String("/platform-tools/adb"ANDROID_EXE_SUFFIX);
246 }
247
248 QString AndroidConfigurations::androidToolPath()
249 {
250 #ifdef Q_OS_WIN32
251     // I want to switch from using android.bat to using an executable. All it really does is call
252     // Java and I've made some progress on it. So if android.exe exists, return that instead.
253     QFileInfo fi(m_config.SDKLocation + QLatin1String("/tools/android"ANDROID_EXE_SUFFIX));
254     if (fi.exists())
255         return m_config.SDKLocation + QString("/tools/android"ANDROID_EXE_SUFFIX);
256     else
257         return m_config.SDKLocation + QLatin1String("/tools/android"ANDROID_BAT_SUFFIX);
258 #else
259     return m_config.SDKLocation + QLatin1String("/tools/android"ANDROID_EXE_SUFFIX);
260 #endif
261 }
262
263 QString AndroidConfigurations::antToolPath()
264 {
265     if (m_config.AntLocation.length())
266         return m_config.AntLocation;
267     else
268         return QLatin1String("ant");
269 }
270
271 QString AndroidConfigurations::emulatorToolPath()
272 {
273     return m_config.SDKLocation + QString("/tools/emulator"ANDROID_EXE_SUFFIX);
274 }
275
276 QString AndroidConfigurations::toolPath(ProjectExplorer::Abi::Architecture architecture)
277 {
278     return m_config.NDKLocation + QString("/toolchains/%1-%2/prebuilt/%3/bin/%4")
279             .arg(toolchainPrefix(architecture))
280             .arg(m_config.NDKToolchainVersion)
281             .arg(ToolchainHost)
282             .arg(toolsPrefix(architecture));
283 }
284
285 QString AndroidConfigurations::stripPath(ProjectExplorer::Abi::Architecture architecture)
286 {
287     return toolPath(architecture) + "-strip"ANDROID_EXE_SUFFIX;
288 }
289
290 QString AndroidConfigurations::readelfPath(ProjectExplorer::Abi::Architecture architecture)
291 {
292     return toolPath(architecture) + "-readelf"ANDROID_EXE_SUFFIX;
293 }
294
295 QString AndroidConfigurations::gccPath(ProjectExplorer::Abi::Architecture architecture)
296 {
297     return toolPath(architecture) + "-gcc"ANDROID_EXE_SUFFIX;
298 }
299
300 QString AndroidConfigurations::gdbServerPath(ProjectExplorer::Abi::Architecture architecture)
301 {
302     QString gdbServerPath;
303     switch (architecture) {
304     case ProjectExplorer::Abi::ArmArchitecture:
305         gdbServerPath = m_config.ArmGdbserverLocation;
306         break;
307     case ProjectExplorer::Abi::X86Architecture:
308         gdbServerPath = m_config.X86GdbserverLocation;
309         break;
310     default:
311         gdbServerPath = Unknown;
312         break;
313     }
314
315     if (gdbServerPath.length())
316         return gdbServerPath;
317     return m_config.NDKLocation+QString("/toolchains/%1-%2/prebuilt/gdbserver")
318             .arg(toolchainPrefix(architecture))
319             .arg(m_config.NDKToolchainVersion);
320 }
321
322 QString AndroidConfigurations::gdbPath(ProjectExplorer::Abi::Architecture architecture)
323 {
324     QString gdbPath;
325     switch (architecture) {
326     case ProjectExplorer::Abi::ArmArchitecture:
327         gdbPath = m_config.ArmGdbLocation;
328         break;
329     case ProjectExplorer::Abi::X86Architecture:
330         gdbPath = m_config.X86GdbLocation;
331         break;
332     default:
333         gdbPath = Unknown;
334         break;
335     }
336     if (!gdbPath.isEmpty())
337         return gdbPath;
338     return toolPath(architecture) + "-gdb"ANDROID_EXE_SUFFIX;
339 }
340
341 QString AndroidConfigurations::openJDKPath()
342 {
343     return m_config.OpenJDKLocation;
344 }
345
346 QString AndroidConfigurations::openJDKBinPath()
347 {
348     if (m_config.OpenJDKLocation.length())
349         return m_config.OpenJDKLocation+"/bin/";
350     return QString();
351 }
352
353 QString AndroidConfigurations::keytoolPath()
354 {
355     return openJDKBinPath()+keytoolName;
356 }
357
358 QString AndroidConfigurations::jarsignerPath()
359 {
360     return openJDKBinPath()+jarsignerName;
361 }
362
363 QString AndroidConfigurations::getDeployDeviceSerialNumber(int &apiLevel)
364 {
365     QVector<AndroidDevice> devices = connectedDevices();
366
367     foreach (AndroidDevice device, devices)
368         if (device.sdk >= apiLevel) {
369             apiLevel = device.sdk;
370             return device.serialNumber;
371         }
372     return startAVD(apiLevel);
373 }
374
375 QVector<AndroidDevice> AndroidConfigurations::connectedDevices(int apiLevel)
376 {
377     QVector<AndroidDevice> devices;
378     QProcess adbProc;
379     adbProc.start(adbToolPath(), QStringList() << "devices");
380     if (!adbProc.waitForFinished(-1)) {
381         adbProc.terminate();
382         return devices;
383     }
384     QList<QByteArray> adbDevs=adbProc.readAll().trimmed().split('\n');
385     adbDevs.removeFirst();
386     AndroidDevice dev;
387     foreach (const QByteArray &device, adbDevs) {
388         dev.serialNumber = device.left(device.indexOf('\t')).trimmed();
389         dev.sdk = getSDKVersion(dev.serialNumber);
390         if (apiLevel != -1 && dev.sdk != apiLevel)
391             continue;
392         devices.push_back(dev);
393     }
394     qSort(devices.begin(), devices.end(), androidDevicesLessThan);
395     return devices;
396 }
397
398 bool AndroidConfigurations::createAVD(int minApiLevel)
399 {
400     QDialog d;
401     Ui::AddNewAVDDialog avdDialog;
402     avdDialog.setupUi(&d);
403     QStringListModel model(sdkTargets(minApiLevel));
404     avdDialog.targetComboBox->setModel(&model);
405     if (!model.rowCount()) {
406         QMessageBox::critical(0, tr("Create AVD error"), tr("Can't create a new AVD, not enough android SDKs available\n"
407                                                             "Please install one SDK with api version >=%1").arg(minApiLevel));
408         return false;
409     }
410
411     QRegExp rx("\\S+");
412     QRegExpValidator v(rx, 0);
413     avdDialog.nameLineEdit->setValidator(&v);
414     if (d.exec() != QDialog::Accepted)
415         return false;
416     return createAVD(avdDialog.targetComboBox->currentText(), avdDialog.nameLineEdit->text(), avdDialog.sizeSpinBox->value());
417 }
418
419 bool AndroidConfigurations::createAVD(const QString &target, const QString &name, int sdcardSize )
420 {
421     QProcess proc;
422     proc.start(androidToolPath(), QStringList() << "create" << "avd" << "-a" << "-t" << target
423                << "-n" << name << "-c" << QString::fromLatin1("%1M").arg(sdcardSize));
424     if (!proc.waitForStarted())
425         return false;
426     proc.write(QByteArray("no\n"));
427     if (!proc.waitForFinished(-1)) {
428         proc.terminate();
429         return false;
430     }
431     return !proc.exitCode();
432 }
433
434 bool AndroidConfigurations::removeAVD(const QString &name)
435 {
436     QProcess proc;
437     proc.start(androidToolPath(), QStringList() << "delete" << "avd" << "-n" << name); // list avaialbe AVDs
438     if (!proc.waitForFinished(-1)) {
439         proc.terminate();
440         return false;
441     }
442     return !proc.exitCode();
443 }
444
445 QVector<AndroidDevice> AndroidConfigurations::androidVirtualDevices()
446 {
447     QVector<AndroidDevice> devices;
448     QProcess proc;
449     proc.start(androidToolPath(), QStringList() << "list" << "avd"); // list available AVDs
450     if (!proc.waitForFinished(-1)) {
451         proc.terminate();
452         return devices;
453     }
454     QList<QByteArray> avds = proc.readAll().trimmed().split('\n');
455     avds.removeFirst();
456     AndroidDevice dev;
457     for (int i = 0; i < avds.size(); i++) {
458         QString line = avds[i];
459         if (!line.contains("Name:"))
460             continue;
461
462         dev.serialNumber = line.mid(line.indexOf(':')+2).trimmed();
463         ++i;
464         for (; i < avds.size(); ++i) {
465             line = avds[i];
466             if (line.contains("---------"))
467                 break;
468             if (line.contains("Target:"))
469                 dev.sdk = line.mid(line.lastIndexOf(' ')).remove(')').toInt();
470             if (line.contains("ABI:"))
471                 dev.cpuABI = line.mid(line.lastIndexOf(' ')).trimmed();
472         }
473         devices.push_back(dev);
474     }
475     qSort(devices.begin(), devices.end(), androidDevicesLessThan);
476
477     return devices;
478 }
479
480 QString AndroidConfigurations::startAVD(int &apiLevel, const QString &name)
481 {
482     QProcess *m_avdProcess = new QProcess();
483     connect(this, SIGNAL(destroyed()), m_avdProcess, SLOT(deleteLater()));
484     connect(m_avdProcess, SIGNAL(finished(int)), m_avdProcess, SLOT(deleteLater()));
485
486     QString avdName = name;
487     QVector<AndroidDevice> devices;
488     bool createAVDOnce=false;
489     while (true) {
490         if (!avdName.length()) {
491             devices = androidVirtualDevices();
492             foreach (AndroidDevice device, devices)
493                 if (device.sdk >= apiLevel) { // take first emulator how supports this package
494                     apiLevel = device.sdk;
495                     avdName = device.serialNumber;
496                     break;
497                 }
498         }
499         // if no emulators found try to create one once
500         if (!avdName.length() && !createAVDOnce) {
501             createAVDOnce = true;
502             QMetaObject::invokeMethod(this,"createAVD", Qt::BlockingQueuedConnection,
503                                       Q_ARG(int, apiLevel));
504         } else {
505             break;
506         }
507     }
508
509     if (avdName.isEmpty())// stop here if no emulators found
510         return avdName;
511
512     // start the emulator
513     m_avdProcess->start(emulatorToolPath(), QStringList() << "-partition-size" << QString::number(config().PartitionSize) << "-avd" << avdName);
514     if (!m_avdProcess->waitForStarted(-1)) {
515         delete m_avdProcess;
516         return QString();
517     }
518
519     // wait until the emulator is online
520     QProcess proc;
521     proc.start(adbToolPath(), QStringList() << QLatin1String("-e") << "wait-for-device");
522     if (!proc.waitForFinished(-1)) {
523         proc.terminate();
524         return QString();
525     }
526     sleep(5);// wait for pm to start
527
528     // workaround for stupid adb bug
529     proc.start(adbToolPath(), QStringList() << QLatin1String("devices"));
530     if (!proc.waitForFinished(-1)) {
531         proc.terminate();
532         return QString();
533     }
534
535     // get connected devices
536     devices = connectedDevices(apiLevel);
537     foreach (AndroidDevice device, devices)
538         if (device.sdk == apiLevel)
539             return device.serialNumber;
540     // this should not happen, but ...
541     return QString();
542 }
543
544 int AndroidConfigurations::getSDKVersion(const QString & device)
545 {
546
547     QProcess adbProc;
548     adbProc.start(adbToolPath(), QStringList()<< "-s" << device << "shell" << "getprop" << "ro.build.version.sdk");
549     if (!adbProc.waitForFinished(-1)) {
550         adbProc.terminate();
551         return -1;
552     }
553     return adbProc.readAll().trimmed().toInt();
554 }
555
556 QString AndroidConfigurations::bestMatch(const QString &targetAPI)
557 {
558     int target = targetAPI.mid(targetAPI.lastIndexOf('-')+1).toInt();
559     foreach (int apiLevel, m_availablePlatforms) {
560         if (apiLevel <= target)
561             return QString("android-%1").arg(apiLevel);
562     }
563     return "android-4";
564 }
565
566 AndroidConfigurations &AndroidConfigurations::instance(QObject *parent)
567 {
568     if (m_instance == 0)
569         m_instance = new AndroidConfigurations(parent);
570     return *m_instance;
571 }
572
573 void AndroidConfigurations::save()
574 {
575     QSettings *settings = Core::ICore::instance()->settings();
576     settings->beginGroup(SettingsGroup);
577     m_config.save(*settings);
578     settings->endGroup();
579 }
580
581 AndroidConfigurations::AndroidConfigurations(QObject *parent)
582     : QObject(parent)
583 {
584     load();
585     updateAvailablePlatforms();
586 }
587
588 void AndroidConfigurations::load()
589 {
590     QSettings *settings = Core::ICore::instance()->settings();
591     settings->beginGroup(SettingsGroup);
592     m_config=AndroidConfig(*settings);
593     settings->endGroup();
594 }
595
596 AndroidConfigurations *AndroidConfigurations::m_instance = 0;
597
598 } // namespace Internal
599 } // namespace Android