/qmake/generators/symbian/symbiancommon.cpp

https://bitbucket.org/ultra_iter/qt-vtl · C++ · 1122 lines · 888 code · 124 blank · 110 comment · 231 complexity · c44b356afdcf0c7ac2cf7720dbb4f8a1 MD5 · raw file

  1. /****************************************************************************
  2. **
  3. ** Copyright (C) 2012 Nokia Corporation and/or its subsidiary(-ies).
  4. ** All rights reserved.
  5. ** Contact: Nokia Corporation (qt-info@nokia.com)
  6. **
  7. ** This file is part of the qmake application of the Qt Toolkit.
  8. **
  9. ** $QT_BEGIN_LICENSE:LGPL$
  10. ** GNU Lesser General Public License Usage
  11. ** This file may be used under the terms of the GNU Lesser General Public
  12. ** License version 2.1 as published by the Free Software Foundation and
  13. ** appearing in the file LICENSE.LGPL included in the packaging of this
  14. ** file. Please review the following information to ensure the GNU Lesser
  15. ** General Public License version 2.1 requirements will be met:
  16. ** http://www.gnu.org/licenses/old-licenses/lgpl-2.1.html.
  17. **
  18. ** In addition, as a special exception, Nokia gives you certain additional
  19. ** rights. These rights are described in the Nokia Qt LGPL Exception
  20. ** version 1.1, included in the file LGPL_EXCEPTION.txt in this package.
  21. **
  22. ** GNU General Public License Usage
  23. ** Alternatively, this file may be used under the terms of the GNU General
  24. ** Public License version 3.0 as published by the Free Software Foundation
  25. ** and appearing in the file LICENSE.GPL included in the packaging of this
  26. ** file. Please review the following information to ensure the GNU General
  27. ** Public License version 3.0 requirements will be met:
  28. ** http://www.gnu.org/copyleft/gpl.html.
  29. **
  30. ** Other Usage
  31. ** Alternatively, this file may be used in accordance with the terms and
  32. ** conditions contained in a signed written agreement between you and Nokia.
  33. **
  34. **
  35. **
  36. **
  37. **
  38. ** $QT_END_LICENSE$
  39. **
  40. ****************************************************************************/
  41. #include "symbiancommon.h"
  42. #include <qdebug.h>
  43. #include <qxmlstream.h>
  44. // Included from tools/shared
  45. #include <symbian/epocroot_p.h>
  46. #define RESOURCE_DIRECTORY_RESOURCE "\\\\resource\\\\apps\\\\"
  47. #define RSS_RULES "RSS_RULES"
  48. #define RSS_RULES_BASE "RSS_RULES."
  49. #define RSS_TAG_NBROFICONS "number_of_icons"
  50. #define RSS_TAG_ICONFILE "icon_file"
  51. #define RSS_TAG_HEADER "header"
  52. #define RSS_TAG_SERVICE_LIST "service_list"
  53. #define RSS_TAG_FILE_OWNERSHIP_LIST "file_ownership_list"
  54. #define RSS_TAG_DATATYPE_LIST "datatype_list"
  55. #define RSS_TAG_FOOTER "footer"
  56. #define RSS_TAG_DEFAULT "default_rules" // Same as just giving rules without tag
  57. #define PLUGIN_COMMON_DEF_FILE_ACTUAL "plugin_commonu.def"
  58. #define MANUFACTURER_NOTE_FILE "manufacturer_note.txt"
  59. #define DEFAULT_MANUFACTURER_NOTE \
  60. "The package is not supported for devices from this manufacturer. Please try the selfsigned " \
  61. "version of the package instead."
  62. SymbianCommonGenerator::SymbianCommonGenerator(MakefileGenerator *generator)
  63. : generator(generator)
  64. {
  65. }
  66. void SymbianCommonGenerator::init()
  67. {
  68. QMakeProject *project = generator->project;
  69. fixedTarget = project->first("QMAKE_ORIG_TARGET");
  70. if (fixedTarget.isEmpty())
  71. fixedTarget = project->first("TARGET");
  72. fixedTarget = generator->unescapeFilePath(fixedTarget);
  73. fixedTarget = removePathSeparators(fixedTarget);
  74. removeSpecialCharacters(fixedTarget);
  75. // This should not be empty since the mkspecs are supposed to set it if missing.
  76. uid3 = project->first("TARGET.UID3").trimmed();
  77. if ((project->values("TEMPLATE")).contains("app"))
  78. targetType = TypeExe;
  79. else if ((project->values("TEMPLATE")).contains("lib")) {
  80. // Check CONFIG to see if we are to build staticlib or dll
  81. if (project->isActiveConfig("staticlib") || project->isActiveConfig("static"))
  82. targetType = TypeLib;
  83. else if (project->isActiveConfig("plugin"))
  84. targetType = TypePlugin;
  85. else
  86. targetType = TypeDll;
  87. } else {
  88. targetType = TypeSubdirs;
  89. }
  90. // UID is valid as either hex or decimal, so just convert it to number and back to hex
  91. // to get proper string for private dir
  92. bool conversionOk = false;
  93. uint uidNum = uid3.toUInt(&conversionOk, 0);
  94. if (!conversionOk) {
  95. fprintf(stderr, "Error: Invalid UID \"%s\".\n", uid3.toUtf8().constData());
  96. } else {
  97. privateDirUid.setNum(uidNum, 16);
  98. while (privateDirUid.length() < 8)
  99. privateDirUid.insert(0, QLatin1Char('0'));
  100. }
  101. }
  102. bool SymbianCommonGenerator::containsStartWithItem(const QChar &c, const QStringList& src)
  103. {
  104. bool result = false;
  105. foreach(QString str, src) {
  106. if (str.startsWith(c)) {
  107. result = true;
  108. break;
  109. }
  110. }
  111. return result;
  112. }
  113. void SymbianCommonGenerator::removeSpecialCharacters(QString& str)
  114. {
  115. // When modifying this method check also symbianRemoveSpecialCharacters in symbian.conf
  116. QString underscore = QLatin1String("_");
  117. str.replace(QLatin1String("/"), underscore);
  118. str.replace(QLatin1String("\\"), underscore);
  119. str.replace(QLatin1String(" "), underscore);
  120. str.replace(QLatin1String(":"), underscore);
  121. }
  122. QString romPath(const QString& path)
  123. {
  124. if(path.length() > 2 && path[1] == ':')
  125. return QLatin1String("z:") + path.mid(2);
  126. return QLatin1String("z:") + path;
  127. }
  128. void SymbianCommonGenerator::generatePkgFile(const QString &iconFile,
  129. bool epocBuild,
  130. const SymbianLocalizationList &symbianLocalizationList)
  131. {
  132. QMakeProject *project = generator->project;
  133. QString pkgFilename = Option::output_dir + QLatin1Char('/') +
  134. QString("%1_template.pkg").arg(fixedTarget);
  135. QFile pkgFile(pkgFilename);
  136. if (!pkgFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
  137. PRINT_FILE_CREATE_ERROR(pkgFilename);
  138. return;
  139. }
  140. QString stubPkgFileName = Option::output_dir + QLatin1Char('/') +
  141. QString("%1_stub.pkg").arg(fixedTarget);
  142. QFile stubPkgFile(stubPkgFileName);
  143. if (!stubPkgFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
  144. PRINT_FILE_CREATE_ERROR(stubPkgFileName);
  145. return;
  146. }
  147. generatedFiles << pkgFile.fileName();
  148. QTextStream t(&pkgFile);
  149. generatedFiles << stubPkgFile.fileName();
  150. QTextStream ts(&stubPkgFile);
  151. QString installerSisHeader = project->values("DEPLOYMENT.installer_header").join("\n");
  152. if (installerSisHeader.isEmpty()) {
  153. // Use correct protected UID for publishing if application UID is in protected range,
  154. // otherwise use self-signable test UID.
  155. QRegExp protUidMatcher("0[xX][0-7].*");
  156. if (protUidMatcher.exactMatch(uid3))
  157. installerSisHeader = QLatin1String("0x2002CCCF");
  158. else
  159. installerSisHeader = QLatin1String("0xA000D7CE"); // Use default self-signable UID
  160. }
  161. QString wrapperStreamBuffer;
  162. QTextStream tw(&wrapperStreamBuffer);
  163. QString dateStr = QDateTime::currentDateTime().toString(Qt::ISODate);
  164. // Header info
  165. QString wrapperPkgFilename = Option::output_dir + QLatin1Char('/') + QString("%1_installer.%2")
  166. .arg(fixedTarget).arg("pkg");
  167. QString headerComment = "; %1 generated by qmake at %2\n"
  168. "; This file is generated by qmake and should not be modified by the user\n"
  169. ";\n\n";
  170. t << headerComment.arg(pkgFilename).arg(dateStr);
  171. tw << headerComment.arg(wrapperPkgFilename).arg(dateStr);
  172. ts << headerComment.arg(stubPkgFileName).arg(dateStr);
  173. QStringList commonRawPreRules;
  174. QStringList mainRawPreRules;
  175. QStringList instRawPreRules;
  176. QStringList stubRawPreRules;
  177. // Though there can't be more than one language or header line, use stringlists
  178. // in case user wants comments to go with the rules.
  179. // Note that it makes no sense to have file specific language or header rules,
  180. // except what is provided for installer header via "DEPLOYMENT.installer_header" variable,
  181. // because stub and main headers should always match. Vendor rules are similarly limited to
  182. // make code cleaner as it is unlikely anyone will want different vendor in different files.
  183. QStringList languageRules;
  184. QStringList headerRules;
  185. QStringList vendorRules;
  186. QStringList commonRawPostRules;
  187. QStringList mainRawPostRules;
  188. QStringList instRawPostRules;
  189. QStringList stubRawPostRules;
  190. QStringList failList; // Used for detecting incorrect usage
  191. QString emptySuffix;
  192. QString mainSuffix(".main");
  193. QString instSuffix(".installer");
  194. QString stubSuffix(".stub");
  195. foreach(QString item, project->values("DEPLOYMENT")) {
  196. parsePreRules(item, emptySuffix, &commonRawPreRules, &languageRules, &headerRules, &vendorRules);
  197. parsePreRules(item, mainSuffix, &mainRawPreRules, &failList, &failList, &failList);
  198. parsePreRules(item, instSuffix, &instRawPreRules, &failList, &failList, &failList);
  199. parsePreRules(item, stubSuffix, &stubRawPreRules, &failList, &failList, &failList);
  200. parsePostRules(item, emptySuffix, &commonRawPostRules);
  201. parsePostRules(item, mainSuffix, &mainRawPostRules);
  202. parsePostRules(item, instSuffix, &instRawPostRules);
  203. parsePostRules(item, stubSuffix, &stubRawPostRules);
  204. }
  205. if (!failList.isEmpty()) {
  206. fprintf(stderr, "Warning: Custom language, header, or vendor definitions are not "
  207. "supported by file specific pkg_prerules.* variables.\n"
  208. "Use plain pkg_prerules and/or DEPLOYMENT.installer_header for customizing "
  209. "these items.\n");
  210. }
  211. foreach(QString item, commonRawPreRules) {
  212. if (item.startsWith("(")) {
  213. // Only regular pkg file should have package dependencies
  214. mainRawPreRules << item;
  215. } else if (item.startsWith("[")) {
  216. // stub pkg file should not have platform dependencies
  217. mainRawPreRules << item;
  218. instRawPreRules << item;
  219. } else {
  220. mainRawPreRules << item;
  221. instRawPreRules << item;
  222. stubRawPreRules << item;
  223. }
  224. }
  225. // Currently common postrules only go to main
  226. mainRawPostRules << commonRawPostRules;
  227. // Apply some defaults if specific data does not exist in PKG pre-rules
  228. if (languageRules.isEmpty()) {
  229. if (symbianLocalizationList.isEmpty()) {
  230. languageRules << "; Language\n&EN\n\n";
  231. } else {
  232. QStringList langCodes;
  233. SymbianLocalizationListIterator iter(symbianLocalizationList);
  234. while (iter.hasNext()) {
  235. const SymbianLocalization &loc = iter.next();
  236. langCodes << loc.symbianLanguageCode;
  237. }
  238. languageRules << QString("; Languages\n&%1\n\n").arg(langCodes.join(","));
  239. }
  240. } else if (headerRules.isEmpty()) {
  241. // In case user defines langs, he must take care also about SIS header
  242. fprintf(stderr, "Warning: If language is defined with DEPLOYMENT pkg_prerules, also the SIS header must be defined\n");
  243. }
  244. t << languageRules.join("\n") << endl;
  245. tw << languageRules.join("\n") << endl;
  246. ts << languageRules.join("\n") << endl;
  247. // Determine application version. If version has missing component values,
  248. // those will default to zero.
  249. // If VERSION is missing altogether or is invalid, use "1,0,0"
  250. QStringList verNumList = project->first("VERSION").split('.');
  251. uint major = 0;
  252. uint minor = 0;
  253. uint patch = 0;
  254. bool success = false;
  255. if (verNumList.size() > 0) {
  256. major = verNumList[0].toUInt(&success);
  257. if (success && verNumList.size() > 1) {
  258. minor = verNumList[1].toUInt(&success);
  259. if (success && verNumList.size() > 2) {
  260. patch = verNumList[2].toUInt(&success);
  261. }
  262. }
  263. }
  264. QString applicationVersion("1,0,0");
  265. if (success)
  266. applicationVersion = QString("%1,%2,%3").arg(major).arg(minor).arg(patch);
  267. // Append package build version number if it is set
  268. QString pkgBuildVersion = project->first("DEPLOYMENT.pkg_build_version");
  269. if (!pkgBuildVersion.isEmpty()) {
  270. success = false;
  271. uint build = pkgBuildVersion.toUInt(&success);
  272. if (success && build < 100) {
  273. if (pkgBuildVersion.size() == 1)
  274. pkgBuildVersion.prepend(QLatin1Char('0'));
  275. applicationVersion.append(pkgBuildVersion);
  276. } else {
  277. fprintf(stderr, "Warning: Invalid DEPLOYMENT.pkg_build_version (%s), must be a number between 0 - 99\n", qPrintable(pkgBuildVersion));
  278. }
  279. }
  280. // Package header
  281. QString sisHeader = "; SIS header: name, uid, version\n#{\"%1\"},(%2),%3\n\n";
  282. QString defaultVisualTarget = project->values("DEPLOYMENT.display_name").join(" ");
  283. if (defaultVisualTarget.isEmpty())
  284. defaultVisualTarget = generator->escapeFilePath(project->first("TARGET"));
  285. defaultVisualTarget = removePathSeparators(defaultVisualTarget);
  286. QString visualTarget = generatePkgNameForHeader(symbianLocalizationList, defaultVisualTarget, false);
  287. QString wrapperTarget = generatePkgNameForHeader(symbianLocalizationList, defaultVisualTarget, true);
  288. if (installerSisHeader.startsWith("0x", Qt::CaseInsensitive)) {
  289. tw << sisHeader.arg(wrapperTarget).arg(installerSisHeader).arg(applicationVersion);
  290. } else {
  291. tw << installerSisHeader << endl;
  292. }
  293. if (headerRules.isEmpty()) {
  294. t << sisHeader.arg(visualTarget).arg(uid3).arg(applicationVersion);
  295. ts << sisHeader.arg(visualTarget).arg(uid3).arg(applicationVersion);
  296. }
  297. else {
  298. t << headerRules.join("\n") << endl;
  299. ts << headerRules.join("\n") << endl;
  300. }
  301. // Vendor name
  302. if (!containsStartWithItem('%', vendorRules)) {
  303. QString vendorStr = QLatin1String("\"Vendor\",");
  304. QString locVendors = vendorStr;
  305. for (int i = 1; i < symbianLocalizationList.size(); i++) {
  306. locVendors.append(vendorStr);
  307. }
  308. locVendors.chop(1);
  309. vendorRules << QString("; Default localized vendor name\n%{%1}\n\n").arg(locVendors);
  310. }
  311. if (!containsStartWithItem(':', vendorRules)) {
  312. vendorRules << "; Default unique vendor name\n:\"Vendor\"\n\n";
  313. }
  314. t << vendorRules.join("\n") << endl;
  315. tw << vendorRules.join("\n") << endl;
  316. ts << vendorRules.join("\n") << endl;
  317. // PKG pre-rules - these are added before actual file installations i.e. SIS package body
  318. QString comment = "\n; Manual PKG pre-rules from PRO files\n";
  319. if (mainRawPreRules.size()) {
  320. t << comment;
  321. t << mainRawPreRules.join("\n") << endl;
  322. }
  323. if (instRawPreRules.size()) {
  324. tw << comment;
  325. tw << instRawPreRules.join("\n") << endl;
  326. }
  327. if (stubRawPreRules.size()) {
  328. ts << comment;
  329. ts << stubRawPreRules.join("\n") << endl;
  330. }
  331. t << endl;
  332. tw << endl;
  333. ts << endl;
  334. // Begin Manufacturer block
  335. if (!project->values("DEPLOYMENT.manufacturers").isEmpty()) {
  336. QString manufacturerStr("IF ");
  337. foreach(QString manufacturer, project->values("DEPLOYMENT.manufacturers")) {
  338. manufacturerStr.append(QString("(MANUFACTURER)=(%1) OR \n ").arg(manufacturer));
  339. }
  340. // Remove the final OR
  341. manufacturerStr.chop(8);
  342. t << manufacturerStr << endl;
  343. }
  344. if (symbianLocalizationList.size()) {
  345. // Add localized resources to DEPLOYMENT if default resource deployment is done
  346. addLocalizedResourcesToDeployment("default_resource_deployment.files", symbianLocalizationList);
  347. }
  348. // deploy files specified by DEPLOYMENT variable
  349. QString remoteTestPath;
  350. QString zDir;
  351. remoteTestPath = QString("!:\\private\\%1").arg(privateDirUid);
  352. if (epocBuild)
  353. zDir = qt_epocRoot() + QLatin1String("epoc32/data/z");
  354. DeploymentList depList;
  355. initProjectDeploySymbian(project, depList, remoteTestPath, true, epocBuild, "$(PLATFORM)", "$(TARGET)", generatedDirs, generatedFiles);
  356. if (depList.size())
  357. t << "; DEPLOYMENT" << endl;
  358. for (int i = 0; i < depList.size(); ++i) {
  359. QString from = depList.at(i).from;
  360. QString to = depList.at(i).to;
  361. QString flags;
  362. bool showOnlyFile = false;
  363. foreach(QString flag, depList.at(i).flags) {
  364. if (flag == QLatin1String("FT")
  365. || flag == QLatin1String("FILETEXT")) {
  366. showOnlyFile = true;
  367. }
  368. flags.append(QLatin1Char(',')).append(flag);
  369. }
  370. if (epocBuild) {
  371. // Deploy anything not already deployed from under epoc32 instead from under
  372. // \epoc32\data\z\ to enable using pkg file without rebuilding
  373. // the project, which can be useful for some binary only distributions.
  374. if (!from.contains(QLatin1String("epoc32"), Qt::CaseInsensitive)) {
  375. from = to;
  376. if (from.size() > 1 && from.at(1) == QLatin1Char(':'))
  377. from = from.mid(2);
  378. from.prepend(zDir);
  379. }
  380. }
  381. // Files with "FILETEXT"/"FT" flag are meant for showing only at installation time
  382. // and therefore do not belong to the stub package and will not install the file into phone.
  383. if (showOnlyFile)
  384. to.clear();
  385. else
  386. ts << QString("\"\" - \"%1\"").arg(romPath(to)) << endl;
  387. t << QString("\"%1\" - \"%2\"%3").arg(from.replace('\\','/')).arg(to).arg(flags) << endl;
  388. }
  389. t << endl;
  390. ts << endl;
  391. // PKG post-rules - these are added after actual file installations i.e. SIS package body
  392. comment = "; Manual PKG post-rules from PRO files\n";
  393. if (mainRawPostRules.size()) {
  394. t << comment;
  395. t << mainRawPostRules.join("\n") << endl;
  396. }
  397. if (instRawPostRules.size()) {
  398. tw << comment;
  399. tw << instRawPostRules.join("\n") << endl;
  400. }
  401. if (stubRawPostRules.size()) {
  402. ts << comment;
  403. ts << stubRawPostRules.join("\n") << endl;
  404. }
  405. // Close Manufacturer block
  406. if (!project->values("DEPLOYMENT.manufacturers").isEmpty()) {
  407. QString manufacturerFailNoteFile;
  408. if (project->values("DEPLOYMENT.manufacturers.fail_note").isEmpty()) {
  409. manufacturerFailNoteFile = QString("%1_" MANUFACTURER_NOTE_FILE).arg(uid3);
  410. QFile ft(manufacturerFailNoteFile);
  411. if (ft.open(QIODevice::WriteOnly)) {
  412. generatedFiles << ft.fileName();
  413. QTextStream t2(&ft);
  414. t2 << QString(DEFAULT_MANUFACTURER_NOTE) << endl;
  415. } else {
  416. PRINT_FILE_CREATE_ERROR(manufacturerFailNoteFile)
  417. }
  418. } else {
  419. manufacturerFailNoteFile = project->values("DEPLOYMENT.manufacturers.fail_note").join("");
  420. }
  421. t << "ELSEIF NOT(0) ; MANUFACTURER" << endl
  422. << "\"" << generator->fileInfo(manufacturerFailNoteFile).absoluteFilePath() << "\""
  423. << " - \"\", FILETEXT, TEXTEXIT" << endl
  424. << "ENDIF ; MANUFACTURER" << endl;
  425. }
  426. // Write wrapper pkg
  427. if (!installerSisHeader.isEmpty()) {
  428. QFile wrapperPkgFile(wrapperPkgFilename);
  429. if (!wrapperPkgFile.open(QIODevice::WriteOnly | QIODevice::Text)) {
  430. PRINT_FILE_CREATE_ERROR(wrapperPkgFilename);
  431. return;
  432. }
  433. generatedFiles << wrapperPkgFile.fileName();
  434. QTextStream twf(&wrapperPkgFile);
  435. twf << wrapperStreamBuffer << endl;
  436. // Wrapped files deployment
  437. QString currentPath = Option::output_dir;
  438. if (!currentPath.endsWith(QLatin1Char('/')))
  439. currentPath += QLatin1Char('/');
  440. QString sisName = QString("%1.sis").arg(fixedTarget);
  441. twf << "\"" << currentPath << sisName << "\" - \"!:\\private\\2002CCCE\\import\\" << sisName << "\"" << endl;
  442. QString bootStrapPath = QLibraryInfo::location(QLibraryInfo::PrefixPath);
  443. bootStrapPath.append("/smartinstaller.sis");
  444. QFileInfo fi(generator->fileInfo(bootStrapPath));
  445. twf << "@\"" << fi.absoluteFilePath() << "\",(0x2002CCCD)" << endl;
  446. }
  447. }
  448. QString SymbianCommonGenerator::removePathSeparators(QString &file)
  449. {
  450. QString ret = file;
  451. if (QDir::separator().unicode() != '/')
  452. ret.replace(QDir::separator(), QLatin1Char('/'));
  453. if (ret.indexOf(QLatin1Char('/')) >= 0)
  454. ret.remove(0, ret.lastIndexOf(QLatin1Char('/')) + 1);
  455. return ret;
  456. }
  457. void SymbianCommonGenerator::writeRegRssFile(QMap<QString, QStringList> &userItems)
  458. {
  459. QString filename(fixedTarget);
  460. filename.append("_reg.rss");
  461. if (!Option::output_dir.isEmpty())
  462. filename = Option::output_dir + '/' + filename;
  463. QFile ft(filename);
  464. if (ft.open(QIODevice::WriteOnly)) {
  465. generatedFiles << ft.fileName();
  466. QTextStream t(&ft);
  467. t << "// ============================================================================" << endl;
  468. t << "// * Generated by qmake (" << qmake_version() << ") (Qt " QT_VERSION_STR ") on: ";
  469. t << QDateTime::currentDateTime().toString(Qt::ISODate) << endl;
  470. t << "// * This file is generated by qmake and should not be modified by the" << endl;
  471. t << "// * user." << endl;
  472. t << "// ============================================================================" << endl;
  473. t << endl;
  474. t << "#include <" << fixedTarget << ".rsg>" << endl;
  475. t << "#include <appinfo.rh>" << endl;
  476. foreach(QString item, userItems[RSS_TAG_HEADER])
  477. t << item << endl;
  478. t << endl;
  479. t << "UID2 KUidAppRegistrationResourceFile" << endl;
  480. t << "UID3 " << uid3 << endl << endl;
  481. t << "RESOURCE APP_REGISTRATION_INFO" << endl;
  482. t << "\t{" << endl;
  483. t << "\tapp_file=\"" << fixedTarget << "\";" << endl;
  484. t << "\tlocalisable_resource_file=\"" RESOURCE_DIRECTORY_RESOURCE << fixedTarget << "\";" << endl;
  485. writeRegRssList(t, userItems[RSS_TAG_SERVICE_LIST],
  486. QLatin1String(RSS_TAG_SERVICE_LIST),
  487. QLatin1String("SERVICE_INFO"));
  488. writeRegRssList(t, userItems[RSS_TAG_FILE_OWNERSHIP_LIST],
  489. QLatin1String(RSS_TAG_FILE_OWNERSHIP_LIST),
  490. QLatin1String("FILE_OWNERSHIP_INFO"));
  491. writeRegRssList(t, userItems[RSS_TAG_DATATYPE_LIST],
  492. QLatin1String(RSS_TAG_DATATYPE_LIST),
  493. QLatin1String("DATATYPE"));
  494. t << endl;
  495. foreach(QString item, userItems[RSS_TAG_DEFAULT])
  496. t << "\t" << item.replace("\n","\n\t") << endl;
  497. t << "\t}" << endl;
  498. foreach(QString item, userItems[RSS_TAG_FOOTER])
  499. t << item << endl;
  500. } else {
  501. PRINT_FILE_CREATE_ERROR(filename)
  502. }
  503. }
  504. void SymbianCommonGenerator::writeRegRssList(QTextStream &t,
  505. QStringList &userList,
  506. const QString &listTag,
  507. const QString &listItem)
  508. {
  509. int itemCount = userList.count();
  510. if (itemCount) {
  511. t << "\t" << listTag << " ="<< endl;
  512. t << "\t\t{" << endl;
  513. foreach(QString item, userList) {
  514. t << "\t\t" << listItem << endl;
  515. t << "\t\t\t{" << endl;
  516. t << "\t\t\t" << item.replace("\n","\n\t\t\t") << endl;
  517. t << "\t\t\t}";
  518. if (--itemCount)
  519. t << ",";
  520. t << endl;
  521. }
  522. t << "\t\t}; "<< endl;
  523. }
  524. }
  525. void SymbianCommonGenerator::writeRssFile(QString &numberOfIcons, QString &iconFile)
  526. {
  527. QString filename(fixedTarget);
  528. if (!Option::output_dir.isEmpty())
  529. filename = Option::output_dir + '/' + filename;
  530. filename.append(".rss");
  531. QFile ft(filename);
  532. if (ft.open(QIODevice::WriteOnly)) {
  533. generatedFiles << ft.fileName();
  534. QTextStream t(&ft);
  535. t << "// ============================================================================" << endl;
  536. t << "// * Generated by qmake (" << qmake_version() << ") (Qt " QT_VERSION_STR ") on: ";
  537. t << QDateTime::currentDateTime().toString(Qt::ISODate) << endl;
  538. t << "// * This file is generated by qmake and should not be modified by the" << endl;
  539. t << "// * user." << endl;
  540. t << "// ============================================================================" << endl;
  541. t << endl;
  542. t << "CHARACTER_SET UTF8" << endl;
  543. t << "#include <appinfo.rh>" << endl;
  544. t << "#include \"" << fixedTarget << ".loc\"" << endl;
  545. t << endl;
  546. t << "RESOURCE LOCALISABLE_APP_INFO r_localisable_app_info" << endl;
  547. t << "\t{" << endl;
  548. t << "\tshort_caption = STRING_r_short_caption;" << endl;
  549. t << "\tcaption_and_icon =" << endl;
  550. t << "\tCAPTION_AND_ICON_INFO" << endl;
  551. t << "\t\t{" << endl;
  552. t << "\t\tcaption = STRING_r_caption;" << endl;
  553. QString rssIconFile = iconFile;
  554. rssIconFile = rssIconFile.replace("/", "\\\\");
  555. if (numberOfIcons.isEmpty() || rssIconFile.isEmpty()) {
  556. // There can be maximum one item in this tag, validated when parsed
  557. t << "\t\tnumber_of_icons = 0;" << endl;
  558. t << "\t\ticon_file = \"\";" << endl;
  559. } else {
  560. // There can be maximum one item in this tag, validated when parsed
  561. t << "\t\tnumber_of_icons = " << numberOfIcons << ";" << endl;
  562. t << "\t\ticon_file = \"" << rssIconFile << "\";" << endl;
  563. }
  564. t << "\t\t};" << endl;
  565. t << "\t}" << endl;
  566. t << endl;
  567. } else {
  568. PRINT_FILE_CREATE_ERROR(filename);
  569. }
  570. }
  571. void SymbianCommonGenerator::writeLocFile(const SymbianLocalizationList &symbianLocalizationList)
  572. {
  573. QString filename = generateLocFileName();
  574. QFile ft(filename);
  575. if (ft.open(QIODevice::WriteOnly)) {
  576. generatedFiles << ft.fileName();
  577. QTextStream t(&ft);
  578. QString displayName = generator->project->values("DEPLOYMENT.display_name").join(" ");
  579. if (displayName.isEmpty())
  580. displayName = generator->escapeFilePath(generator->project->first("TARGET"));
  581. t << "// ============================================================================" << endl;
  582. t << "// * Generated by qmake (" << qmake_version() << ") (Qt " QT_VERSION_STR ") on: ";
  583. t << QDateTime::currentDateTime().toString(Qt::ISODate) << endl;
  584. t << "// * This file is generated by qmake and should not be modified by the" << endl;
  585. t << "// * user." << endl;
  586. t << "// ============================================================================" << endl;
  587. t << endl;
  588. t << "#ifdef LANGUAGE_SC" << endl;
  589. t << "#define STRING_r_short_caption \"" << displayName << "\"" << endl;
  590. t << "#define STRING_r_caption \"" << displayName << "\"" << endl;
  591. SymbianLocalizationListIterator iter(symbianLocalizationList);
  592. while (iter.hasNext()) {
  593. const SymbianLocalization &loc = iter.next();
  594. QString shortCaption = loc.shortCaption;
  595. QString longCaption = loc.longCaption;
  596. if (shortCaption.isEmpty())
  597. shortCaption = displayName;
  598. if (longCaption.isEmpty())
  599. longCaption = displayName;
  600. t << "#elif defined LANGUAGE_" << loc.symbianLanguageCode << endl;
  601. t << "#define STRING_r_short_caption \"" << shortCaption << "\"" << endl;
  602. t << "#define STRING_r_caption \"" << longCaption << "\"" << endl;
  603. }
  604. t << "#else" << endl;
  605. t << "#define STRING_r_short_caption \"" << displayName << "\"" << endl;
  606. t << "#define STRING_r_caption \"" << displayName << "\"" << endl;
  607. t << "#endif" << endl;
  608. } else {
  609. PRINT_FILE_CREATE_ERROR(filename);
  610. }
  611. }
  612. void SymbianCommonGenerator::readRssRules(QString &numberOfIcons,
  613. QString &iconFile, QMap<QString,
  614. QStringList> &userRssRules)
  615. {
  616. QMakeProject *project = generator->project;
  617. for (QMap<QString, QStringList>::iterator it = project->variables().begin(); it != project->variables().end(); ++it) {
  618. if (it.key().startsWith(RSS_RULES_BASE)) {
  619. QString newKey = it.key().mid(sizeof(RSS_RULES_BASE) - 1);
  620. if (newKey.isEmpty()) {
  621. fprintf(stderr, "Warning: Empty RSS_RULES_BASE key encountered\n");
  622. continue;
  623. }
  624. QStringList newValues;
  625. QStringList values = it.value();
  626. foreach(QString item, values) {
  627. // If there is no stringlist defined for a rule, use rule value directly
  628. // This is convenience for defining single line statements
  629. if (project->values(item).isEmpty()) {
  630. newValues << item;
  631. } else {
  632. QStringList itemList;
  633. foreach(QString itemRow, project->values(item)) {
  634. itemList << itemRow;
  635. }
  636. newValues << itemList.join("\n");
  637. }
  638. }
  639. // Verify that there is exactly one value in RSS_TAG_NBROFICONS
  640. if (newKey == RSS_TAG_NBROFICONS) {
  641. if (newValues.count() == 1) {
  642. numberOfIcons = newValues[0];
  643. } else {
  644. fprintf(stderr, "Warning: There must be exactly one value in '%s%s'\n",
  645. RSS_RULES_BASE, RSS_TAG_NBROFICONS);
  646. continue;
  647. }
  648. // Verify that there is exactly one value in RSS_TAG_ICONFILE
  649. } else if (newKey == RSS_TAG_ICONFILE) {
  650. if (newValues.count() == 1) {
  651. iconFile = newValues[0];
  652. } else {
  653. fprintf(stderr, "Warning: There must be exactly one value in '%s%s'\n",
  654. RSS_RULES_BASE, RSS_TAG_ICONFILE);
  655. continue;
  656. }
  657. } else if (newKey == RSS_TAG_HEADER
  658. || newKey == RSS_TAG_SERVICE_LIST
  659. || newKey == RSS_TAG_FILE_OWNERSHIP_LIST
  660. || newKey == RSS_TAG_DATATYPE_LIST
  661. || newKey == RSS_TAG_FOOTER
  662. || newKey == RSS_TAG_DEFAULT) {
  663. userRssRules[newKey] = newValues;
  664. continue;
  665. } else {
  666. fprintf(stderr, "Warning: Unsupported key:'%s%s'\n",
  667. RSS_RULES_BASE, newKey.toLatin1().constData());
  668. continue;
  669. }
  670. }
  671. }
  672. QStringList newValues;
  673. foreach(QString item, project->values(RSS_RULES)) {
  674. // If there is no stringlist defined for a rule, use rule value directly
  675. // This is convenience for defining single line statements
  676. if (project->values(item).isEmpty()) {
  677. newValues << item;
  678. } else {
  679. newValues << project->values(item);
  680. }
  681. }
  682. userRssRules[RSS_TAG_DEFAULT] << newValues;
  683. // Validate that either both RSS_TAG_NBROFICONS and RSS_TAG_ICONFILE keys exist
  684. // or neither of them exist
  685. if (!((numberOfIcons.isEmpty() && iconFile.isEmpty()) ||
  686. (!numberOfIcons.isEmpty() && !iconFile.isEmpty()))) {
  687. numberOfIcons.clear();
  688. iconFile.clear();
  689. fprintf(stderr, "Warning: Both or neither of '%s%s' and '%s%s' keys must exist.\n",
  690. RSS_RULES_BASE, RSS_TAG_NBROFICONS, RSS_RULES_BASE, RSS_TAG_ICONFILE);
  691. }
  692. // Validate that RSS_TAG_NBROFICONS contains only numbers
  693. if (!numberOfIcons.isEmpty()) {
  694. bool ok;
  695. numberOfIcons = numberOfIcons.simplified();
  696. numberOfIcons.toInt(&ok);
  697. if (!ok) {
  698. numberOfIcons.clear();
  699. iconFile.clear();
  700. fprintf(stderr, "Warning: '%s%s' must be integer in decimal format.\n",
  701. RSS_RULES_BASE, RSS_TAG_NBROFICONS);
  702. }
  703. }
  704. }
  705. void SymbianCommonGenerator::writeCustomDefFile()
  706. {
  707. if (targetType == TypePlugin && !generator->project->isActiveConfig("stdbinary")) {
  708. // Create custom def file for plugin
  709. QFile ft(Option::output_dir + QLatin1Char('/') + QLatin1String(PLUGIN_COMMON_DEF_FILE_ACTUAL));
  710. if (ft.open(QIODevice::WriteOnly)) {
  711. generatedFiles << ft.fileName();
  712. QTextStream t(&ft);
  713. t << "; ==============================================================================" << endl;
  714. t << "; Generated by qmake (" << qmake_version() << ") (Qt " QT_VERSION_STR ") on: ";
  715. t << QDateTime::currentDateTime().toString(Qt::ISODate) << endl;
  716. t << "; This file is generated by qmake and should not be modified by the" << endl;
  717. t << "; user." << endl;
  718. t << "; Name : " PLUGIN_COMMON_DEF_FILE_ACTUAL << endl;
  719. t << "; Part of : " << generator->project->values("TARGET").join(" ") << endl;
  720. t << "; Description : Fixes common plugin symbols to known ordinals" << endl;
  721. t << "; Version : " << endl;
  722. t << ";" << endl;
  723. t << "; ==============================================================================" << "\n" << endl;
  724. t << endl;
  725. t << "EXPORTS" << endl;
  726. t << "\tqt_plugin_query_verification_data @ 1 NONAME" << endl;
  727. t << "\tqt_plugin_instance @ 2 NONAME" << endl;
  728. t << endl;
  729. } else {
  730. PRINT_FILE_CREATE_ERROR(QString(PLUGIN_COMMON_DEF_FILE_ACTUAL))
  731. }
  732. }
  733. }
  734. void SymbianCommonGenerator::parseTsFiles(SymbianLocalizationList *symbianLocalizationList)
  735. {
  736. if (!generator->project->isActiveConfig("localize_deployment")) {
  737. return;
  738. }
  739. QStringList symbianTsFiles;
  740. symbianTsFiles << generator->project->values("SYMBIAN_MATCHED_TRANSLATIONS");
  741. if (!symbianTsFiles.isEmpty()) {
  742. fillQt2SymbianLocalizationList(symbianLocalizationList);
  743. QMutableListIterator<SymbianLocalization> iter(*symbianLocalizationList);
  744. while (iter.hasNext()) {
  745. SymbianLocalization &loc = iter.next();
  746. static QString matchStrTemplate = QLatin1String(".*_%1\\.ts");
  747. QString matchStr = matchStrTemplate.arg(loc.qtLanguageCode);
  748. foreach (QString file, symbianTsFiles) {
  749. QRegExp matcher(matchStr);
  750. matcher.setCaseSensitivity(Qt::CaseInsensitive);
  751. if (matcher.exactMatch(file) && parseTsContent(file, &loc))
  752. break;
  753. }
  754. }
  755. }
  756. }
  757. void SymbianCommonGenerator::fillQt2SymbianLocalizationList(SymbianLocalizationList *symbianLocalizationList)
  758. {
  759. static QString symbianCodePrefix = QLatin1String("SYMBIAN_LANG.");
  760. QStringList symbianLanguages = generator->project->values("SYMBIAN_MATCHED_LANGUAGES");
  761. foreach (QString qtCode, symbianLanguages) {
  762. QString symbianCodeVariable = symbianCodePrefix + qtCode;
  763. QStringList symbianCodes = generator->project->values(symbianCodeVariable);
  764. // Some languages have more than one Symbian code, so they get more than one
  765. // entry in symbianLocalizationList.
  766. foreach (QString symbianCode, symbianCodes) {
  767. SymbianLocalization newLoc;
  768. newLoc.symbianLanguageCode = symbianCode;
  769. if (!newLoc.symbianLanguageCode.isEmpty()) {
  770. newLoc.qtLanguageCode = qtCode;
  771. symbianLocalizationList->append(newLoc);
  772. }
  773. }
  774. }
  775. }
  776. void SymbianCommonGenerator::parsePreRules(const QString &deploymentVariable,
  777. const QString &variableSuffix,
  778. QStringList *rawRuleList,
  779. QStringList *languageRuleList,
  780. QStringList *headerRuleList,
  781. QStringList *vendorRuleList)
  782. {
  783. QMakeProject *project = generator->project;
  784. foreach(QString pkgrulesItem, project->values(deploymentVariable + ".pkg_prerules" + variableSuffix)) {
  785. QStringList pkgrulesValue = project->values(pkgrulesItem);
  786. // If there is no stringlist defined for a rule, use rule name directly
  787. // This is convenience for defining single line statements
  788. if (pkgrulesValue.isEmpty()) {
  789. if (pkgrulesItem.startsWith("&"))
  790. *languageRuleList << pkgrulesItem;
  791. else if (pkgrulesItem.startsWith("#"))
  792. *headerRuleList << pkgrulesItem;
  793. else if (pkgrulesItem.startsWith("%") || pkgrulesItem.startsWith(":"))
  794. *vendorRuleList << pkgrulesItem;
  795. else
  796. *rawRuleList << pkgrulesItem;
  797. } else {
  798. if (containsStartWithItem('&', pkgrulesValue)) {
  799. foreach(QString pkgrule, pkgrulesValue) {
  800. *languageRuleList << pkgrule;
  801. }
  802. } else if (containsStartWithItem('#', pkgrulesValue)) {
  803. foreach(QString pkgrule, pkgrulesValue) {
  804. *headerRuleList << pkgrule;
  805. }
  806. } else if (containsStartWithItem('%', pkgrulesValue)
  807. || containsStartWithItem(':', pkgrulesValue)) {
  808. foreach(QString pkgrule, pkgrulesValue) {
  809. *vendorRuleList << pkgrule;
  810. }
  811. } else {
  812. foreach(QString pkgrule, pkgrulesValue) {
  813. *rawRuleList << pkgrule;
  814. }
  815. }
  816. }
  817. }
  818. }
  819. void SymbianCommonGenerator::parsePostRules(const QString &deploymentVariable,
  820. const QString &variableSuffix,
  821. QStringList *rawRuleList)
  822. {
  823. QMakeProject *project = generator->project;
  824. foreach(QString pkgrulesItem, project->values(deploymentVariable + ".pkg_postrules" + variableSuffix)) {
  825. QStringList pkgrulesValue = project->values(pkgrulesItem);
  826. // If there is no stringlist defined for a rule, use rule name directly
  827. // This is convenience for defining single line statements
  828. if (pkgrulesValue.isEmpty()) {
  829. *rawRuleList << pkgrulesItem;
  830. } else {
  831. foreach(QString pkgrule, pkgrulesValue) {
  832. *rawRuleList << pkgrule;
  833. }
  834. }
  835. }
  836. }
  837. bool SymbianCommonGenerator::parseTsContent(const QString &tsFilename, SymbianLocalization *loc)
  838. {
  839. bool retval = true;
  840. QMakeProject *project = generator->project;
  841. QFile tsFile(tsFilename);
  842. if (tsFile.exists()) {
  843. if (tsFile.open(QIODevice::ReadOnly)) {
  844. static QString applicationCaptionsContext = QLatin1String("QtApplicationCaptions");
  845. static QString pkgNameContext = QLatin1String("QtPackageNames");
  846. static QString tsElement = QLatin1String("TS");
  847. static QString contextElement = QLatin1String("context");
  848. static QString nameElement = QLatin1String("name");
  849. static QString messageElement = QLatin1String("message");
  850. static QString sourceElement = QLatin1String("source");
  851. static QString translationElement = QLatin1String("translation");
  852. static QString idAttribute = QLatin1String("id");
  853. static QString shortCaptionId = QLatin1String("qtn_short_caption_");
  854. static QString longCaptionId = QLatin1String("qtn_long_caption_");
  855. static QString pkgDisplayNameId = QLatin1String("qtn_package_name_");
  856. static QString installerPkgDisplayNameId = QLatin1String("qtn_smart_installer_package_name_");
  857. static QString shortCaptionSource = QLatin1String("Application short caption");
  858. static QString longCaptionSource = QLatin1String("Application long caption");
  859. static QString pkgDisplayNameSource = QLatin1String("Package name");
  860. static QString installerPkgDisplayNameSource = QLatin1String("Smart installer package name");
  861. enum CurrentContext {
  862. ContextUnknown,
  863. ContextUninteresting,
  864. ContextInteresting
  865. };
  866. QXmlStreamReader xml(&tsFile);
  867. while (!xml.atEnd() && xml.name() != tsElement)
  868. xml.readNextStartElement();
  869. while (xml.readNextStartElement()) {
  870. if (xml.name() == contextElement) {
  871. CurrentContext currentContext = ContextUnknown;
  872. while (xml.readNextStartElement()) {
  873. if (currentContext == ContextUnknown) {
  874. // Expect name element before message elements
  875. if (xml.name() == nameElement) {
  876. QString nameText = xml.readElementText();
  877. if (nameText == applicationCaptionsContext || nameText == pkgNameContext) {
  878. currentContext = ContextInteresting;
  879. } else {
  880. currentContext = ContextUninteresting;
  881. }
  882. } else {
  883. xml.skipCurrentElement();
  884. }
  885. } else if (currentContext == ContextInteresting) {
  886. if (xml.name() == messageElement) {
  887. QString source;
  888. QString translation;
  889. QString id = xml.attributes().value(idAttribute).toString();
  890. while (xml.readNextStartElement()) {
  891. if (xml.name() == sourceElement) {
  892. source = xml.readElementText();
  893. } else if (xml.name() == translationElement) {
  894. // Technically translation element can have child elements
  895. // i.e. numerusform and lengthvariant. We don't support
  896. // these for actual caption/pkgname translations, but since
  897. // they may be present on other unrelated message elements,
  898. // we need to explicitly skip them to avoid parsing errors.
  899. translation = xml.readElementText(QXmlStreamReader::SkipChildElements);
  900. } else {
  901. xml.skipCurrentElement();
  902. }
  903. }
  904. // Interesting translations can be identified either by id attribute
  905. // of the message or by the source text.
  906. // Allow translations with correct id to override translations
  907. // detected by source text, as the source text can accidentally
  908. // be the same in another string if there are non-interesting
  909. // translations added to same context.
  910. if (id.startsWith(shortCaptionId)
  911. || (loc->shortCaption.isEmpty() && source == shortCaptionSource)) {
  912. loc->shortCaption = translation;
  913. } else if (id.startsWith(longCaptionId)
  914. || (loc->longCaption.isEmpty() && source == longCaptionSource)) {
  915. loc->longCaption = translation;
  916. } else if (id.startsWith(pkgDisplayNameId)
  917. || (loc->pkgDisplayName.isEmpty() && source == pkgDisplayNameSource)) {
  918. loc->pkgDisplayName = translation;
  919. } else if (id.startsWith(installerPkgDisplayNameId)
  920. || (loc->installerPkgDisplayName.isEmpty() && source == installerPkgDisplayNameSource)) {
  921. loc->installerPkgDisplayName = translation;
  922. }
  923. } else {
  924. xml.skipCurrentElement();
  925. }
  926. } else {
  927. xml.skipCurrentElement();
  928. }
  929. }
  930. } else {
  931. xml.skipCurrentElement();
  932. }
  933. }
  934. if (xml.hasError()) {
  935. retval = false;
  936. fprintf(stderr, "ERROR: Encountered error \"%s\" when parsing ts file (%s).\n",
  937. qPrintable(xml.errorString()), qPrintable(tsFilename));
  938. }
  939. } else {
  940. retval = false;
  941. fprintf(stderr, "Warning: Could not open ts file (%s).\n", qPrintable(tsFilename));
  942. }
  943. } else {
  944. retval = false;
  945. fprintf(stderr, "Warning: ts file does not exist: (%s), unable to parse it.\n",
  946. qPrintable(tsFilename));
  947. }
  948. return retval;
  949. }
  950. QString SymbianCommonGenerator::generatePkgNameForHeader(const SymbianLocalizationList &symbianLocalizationList,
  951. const QString &defaultName,
  952. bool isForSmartInstaller)
  953. {
  954. QStringList allNames;
  955. QString noTranslation = defaultName;
  956. if (isForSmartInstaller)
  957. noTranslation += QLatin1String(" installer");
  958. SymbianLocalizationListIterator iter(symbianLocalizationList);
  959. while (iter.hasNext()) {
  960. const SymbianLocalization &loc = iter.next();
  961. QString currentName;
  962. if (isForSmartInstaller) {
  963. currentName = loc.installerPkgDisplayName;
  964. } else {
  965. currentName = loc.pkgDisplayName;
  966. }
  967. if (currentName.isEmpty())
  968. currentName = noTranslation;
  969. allNames << currentName;
  970. }
  971. if (!allNames.size())
  972. allNames << noTranslation;
  973. return allNames.join("\",\"");
  974. }
  975. void SymbianCommonGenerator::addLocalizedResourcesToDeployment(const QString &deploymentFilesVar,
  976. const SymbianLocalizationList &symbianLocalizationList)
  977. {
  978. QStringList locResources;
  979. foreach (QString defaultResource, generator->project->values(deploymentFilesVar)) {
  980. if (defaultResource.endsWith(".rsc")) {
  981. defaultResource.chop(2);
  982. SymbianLocalizationListIterator iter(symbianLocalizationList);
  983. while (iter.hasNext()) {
  984. const SymbianLocalization &loc = iter.next();
  985. locResources << QString(defaultResource + loc.symbianLanguageCode);
  986. }
  987. }
  988. }
  989. generator->project->values(deploymentFilesVar) << locResources;
  990. }
  991. QString SymbianCommonGenerator::generateLocFileName()
  992. {
  993. QString fileName(fixedTarget);
  994. if (!Option::output_dir.isEmpty())
  995. fileName = Option::output_dir + QLatin1Char('/') + fileName;
  996. fileName.append(".loc");
  997. return fileName;
  998. }