The JUCE cross-platform C++ framework, with DISTRHO/KXStudio specific changes
You can not select more than 25 topics Topics must start with a letter or number, can include dashes ('-') and can be up to 35 characters long.

911 lines
37KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE 6 technical preview.
  4. Copyright (c) 2020 - Raw Material Software Limited
  5. You may use this code under the terms of the GPL v3
  6. (see www.gnu.org/licenses).
  7. For this technical preview, this file is not subject to commercial licensing.
  8. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  9. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  10. DISCLAIMED.
  11. ==============================================================================
  12. */
  13. #include "jucer_Headers.h"
  14. #include "jucer_Application.h"
  15. #include "../Utility/Helpers/jucer_TranslationHelpers.h"
  16. #include "jucer_CommandLine.h"
  17. //==============================================================================
  18. const char* preferredLineFeed = "\r\n";
  19. const char* getPreferredLineFeed() { return preferredLineFeed; }
  20. //==============================================================================
  21. namespace
  22. {
  23. static void hideDockIcon()
  24. {
  25. #if JUCE_MAC
  26. Process::setDockIconVisible (false);
  27. #endif
  28. }
  29. static Array<File> findAllSourceFiles (const File& folder)
  30. {
  31. Array<File> files;
  32. for (const auto& di : RangedDirectoryIterator (folder, true, "*.cpp;*.cxx;*.cc;*.c;*.h;*.hpp;*.hxx;*.hpp;*.mm;*.m;*.java;*.dox;*.soul;*.js", File::findFiles))
  33. if (! di.getFile().isSymbolicLink())
  34. files.add (di.getFile());
  35. return files;
  36. }
  37. static void replaceFile (const File& file, const String& newText, const String& message)
  38. {
  39. std::cout << message << file.getFullPathName() << std::endl;
  40. TemporaryFile temp (file);
  41. if (! temp.getFile().replaceWithText (newText, false, false, nullptr))
  42. ConsoleApplication::fail ("!!! ERROR Couldn't write to temp file!");
  43. if (! temp.overwriteTargetFileWithTemporary())
  44. ConsoleApplication::fail ("!!! ERROR Couldn't write to file!");
  45. }
  46. //==============================================================================
  47. struct LoadedProject
  48. {
  49. explicit LoadedProject (const ArgumentList::Argument& fileToLoad)
  50. {
  51. hideDockIcon();
  52. auto projectFile = fileToLoad.resolveAsExistingFile();
  53. if (! projectFile.hasFileExtension (Project::projectFileExtension))
  54. ConsoleApplication::fail (projectFile.getFullPathName() + " isn't a valid jucer project file!");
  55. project.reset (new Project (projectFile));
  56. if (! project->loadFrom (projectFile, true, false))
  57. {
  58. project.reset();
  59. ConsoleApplication::fail ("Failed to load the project file: " + projectFile.getFullPathName());
  60. }
  61. preferredLineFeed = project->getProjectLineFeed().toRawUTF8();
  62. }
  63. void save (bool justSaveResources)
  64. {
  65. if (project != nullptr)
  66. {
  67. if (! justSaveResources)
  68. rescanModulePathsIfNecessary();
  69. auto error = justSaveResources ? project->saveResourcesOnly()
  70. : project->saveProject();
  71. project.reset();
  72. if (error.failed())
  73. ConsoleApplication::fail ("Error when saving: " + error.getErrorMessage());
  74. }
  75. }
  76. void rescanModulePathsIfNecessary()
  77. {
  78. bool scanJUCEPath = false, scanUserPaths = false;
  79. const auto& modules = project->getEnabledModules();
  80. for (auto i = modules.getNumModules(); --i >= 0;)
  81. {
  82. const auto& id = modules.getModuleID (i);
  83. if (isJUCEModule (id) && ! scanJUCEPath)
  84. {
  85. if (modules.shouldUseGlobalPath (id))
  86. scanJUCEPath = true;
  87. }
  88. else if (! scanUserPaths)
  89. {
  90. if (modules.shouldUseGlobalPath (id))
  91. scanUserPaths = true;
  92. }
  93. }
  94. if (scanJUCEPath)
  95. ProjucerApplication::getApp().rescanJUCEPathModules();
  96. if (scanUserPaths)
  97. ProjucerApplication::getApp().rescanUserPathModules();
  98. }
  99. std::unique_ptr<Project> project;
  100. };
  101. //==============================================================================
  102. /* Running a command-line of the form "projucer --resave foobar.jucer" will try to load
  103. that project and re-export all of its targets.
  104. */
  105. static void resaveProject (const ArgumentList& args, bool justSaveResources)
  106. {
  107. args.checkMinNumArguments (2);
  108. LoadedProject proj (args[1]);
  109. std::cout << (justSaveResources ? "Re-saving project resources: "
  110. : "Re-saving file: ")
  111. << proj.project->getFile().getFullPathName() << std::endl;
  112. proj.save (justSaveResources);
  113. }
  114. //==============================================================================
  115. static void getVersion (const ArgumentList& args)
  116. {
  117. args.checkMinNumArguments (2);
  118. LoadedProject proj (args[1]);
  119. std::cout << proj.project->getVersionString() << std::endl;
  120. }
  121. //==============================================================================
  122. static void setVersion (const ArgumentList& args)
  123. {
  124. args.checkMinNumArguments (2);
  125. LoadedProject proj (args[2]);
  126. String version (args[1].text.trim());
  127. std::cout << "Setting project version: " << version << std::endl;
  128. proj.project->setProjectVersion (version);
  129. proj.save (false);
  130. }
  131. //==============================================================================
  132. static void bumpVersion (const ArgumentList& args)
  133. {
  134. args.checkMinNumArguments (2);
  135. LoadedProject proj (args[1]);
  136. String version = proj.project->getVersionString();
  137. version = version.upToLastOccurrenceOf (".", true, false)
  138. + String (version.getTrailingIntValue() + 1);
  139. std::cout << "Bumping project version to: " << version << std::endl;
  140. proj.project->setProjectVersion (version);
  141. proj.save (false);
  142. }
  143. static void gitTag (const ArgumentList& args)
  144. {
  145. args.checkMinNumArguments (2);
  146. LoadedProject proj (args[1]);
  147. String version (proj.project->getVersionString());
  148. if (version.trim().isEmpty())
  149. ConsoleApplication::fail ("Cannot read version number from project!");
  150. StringArray command;
  151. command.add ("git");
  152. command.add ("tag");
  153. command.add ("-a");
  154. command.add (version);
  155. command.add ("-m");
  156. command.add (version.quoted());
  157. std::cout << "Performing command: " << command.joinIntoString(" ") << std::endl;
  158. ChildProcess c;
  159. if (! c.start (command, 0))
  160. ConsoleApplication::fail ("Cannot run git!");
  161. c.waitForProcessToFinish (10000);
  162. if (c.getExitCode() != 0)
  163. ConsoleApplication::fail ("git command failed!");
  164. }
  165. //==============================================================================
  166. static void showStatus (const ArgumentList& args)
  167. {
  168. args.checkMinNumArguments (2);
  169. LoadedProject proj (args[1]);
  170. std::cout << "Project file: " << proj.project->getFile().getFullPathName() << std::endl
  171. << "Name: " << proj.project->getProjectNameString() << std::endl
  172. << "UID: " << proj.project->getProjectUIDString() << std::endl;
  173. auto& modules = proj.project->getEnabledModules();
  174. if (int numModules = modules.getNumModules())
  175. {
  176. std::cout << "Modules:" << std::endl;
  177. for (int i = 0; i < numModules; ++i)
  178. std::cout << " " << modules.getModuleID (i) << std::endl;
  179. }
  180. }
  181. //==============================================================================
  182. static String getModulePackageName (const LibraryModule& module)
  183. {
  184. return module.getID() + ".jucemodule";
  185. }
  186. static void zipModule (const File& targetFolder, const File& moduleFolder)
  187. {
  188. jassert (targetFolder.isDirectory());
  189. auto moduleFolderParent = moduleFolder.getParentDirectory();
  190. LibraryModule module (moduleFolder);
  191. if (! module.isValid())
  192. ConsoleApplication::fail (moduleFolder.getFullPathName() + " is not a valid module folder!");
  193. auto targetFile = targetFolder.getChildFile (getModulePackageName (module));
  194. ZipFile::Builder zip;
  195. {
  196. for (const auto& i : RangedDirectoryIterator (moduleFolder, true, "*", File::findFiles))
  197. if (! i.getFile().isHidden())
  198. zip.addFile (i.getFile(), 9, i.getFile().getRelativePathFrom (moduleFolderParent));
  199. }
  200. std::cout << "Writing: " << targetFile.getFullPathName() << std::endl;
  201. TemporaryFile temp (targetFile);
  202. {
  203. FileOutputStream out (temp.getFile());
  204. if (! (out.openedOk() && zip.writeToStream (out, nullptr)))
  205. ConsoleApplication::fail ("Failed to write to the target file: " + targetFile.getFullPathName());
  206. }
  207. if (! temp.overwriteTargetFileWithTemporary())
  208. ConsoleApplication::fail ("Failed to write to the target file: " + targetFile.getFullPathName());
  209. }
  210. static void buildModules (const ArgumentList& args, const bool buildAllWithIndex)
  211. {
  212. hideDockIcon();
  213. args.checkMinNumArguments (3);
  214. auto targetFolder = args[1].resolveAsFile();
  215. if (! targetFolder.isDirectory())
  216. ConsoleApplication::fail ("The first argument must be the directory to put the result.");
  217. if (buildAllWithIndex)
  218. {
  219. auto folderToSearch = args[2].resolveAsFile();
  220. var infoList;
  221. for (const auto& i : RangedDirectoryIterator (folderToSearch, false, "*", File::findDirectories))
  222. {
  223. LibraryModule module (i.getFile());
  224. if (module.isValid())
  225. {
  226. zipModule (targetFolder, i.getFile());
  227. var moduleInfo (new DynamicObject());
  228. moduleInfo.getDynamicObject()->setProperty ("file", getModulePackageName (module));
  229. moduleInfo.getDynamicObject()->setProperty ("info", module.moduleInfo.getModuleInfo());
  230. infoList.append (moduleInfo);
  231. }
  232. }
  233. auto indexFile = targetFolder.getChildFile ("modulelist");
  234. std::cout << "Writing: " << indexFile.getFullPathName() << std::endl;
  235. indexFile.replaceWithText (JSON::toString (infoList), false, false);
  236. }
  237. else
  238. {
  239. for (int i = 2; i < args.size(); ++i)
  240. zipModule (targetFolder, args[i].resolveAsFile());
  241. }
  242. }
  243. //==============================================================================
  244. struct CleanupOptions
  245. {
  246. bool removeTabs;
  247. bool fixDividerComments;
  248. };
  249. static void cleanWhitespace (const File& file, CleanupOptions options)
  250. {
  251. auto content = file.loadFileAsString();
  252. auto isProjucerTemplateFile = [file, content]
  253. {
  254. return file.getFullPathName().contains ("Templates")
  255. && content.contains ("%""%") && content.contains ("//[");
  256. }();
  257. if (isProjucerTemplateFile)
  258. return;
  259. StringArray lines;
  260. lines.addLines (content);
  261. bool anyTabsRemoved = false;
  262. for (int i = 0; i < lines.size(); ++i)
  263. {
  264. String& line = lines.getReference (i);
  265. if (options.removeTabs && line.containsChar ('\t'))
  266. {
  267. anyTabsRemoved = true;
  268. for (;;)
  269. {
  270. const int tabPos = line.indexOfChar ('\t');
  271. if (tabPos < 0)
  272. break;
  273. const int spacesPerTab = 4;
  274. const int spacesNeeded = spacesPerTab - (tabPos % spacesPerTab);
  275. line = line.replaceSection (tabPos, 1, String::repeatedString (" ", spacesNeeded));
  276. }
  277. }
  278. if (options.fixDividerComments)
  279. {
  280. auto afterIndent = line.trim();
  281. if (afterIndent.startsWith ("//") && afterIndent.length() > 20)
  282. {
  283. afterIndent = afterIndent.substring (2);
  284. if (afterIndent.containsOnly ("=")
  285. || afterIndent.containsOnly ("/")
  286. || afterIndent.containsOnly ("-"))
  287. {
  288. line = line.substring (0, line.indexOfChar ('/'))
  289. + "//" + String::repeatedString ("=", 78);
  290. }
  291. }
  292. }
  293. line = line.trimEnd();
  294. }
  295. if (options.removeTabs && ! anyTabsRemoved)
  296. return;
  297. auto newText = joinLinesIntoSourceFile (lines);
  298. if (newText != content && newText != content + getPreferredLineFeed())
  299. replaceFile (file, newText, options.removeTabs ? "Removing tabs in: "
  300. : "Cleaning file: ");
  301. }
  302. static void scanFilesForCleanup (const ArgumentList& args, CleanupOptions options)
  303. {
  304. args.checkMinNumArguments (2);
  305. for (auto it = args.arguments.begin() + 1; it < args.arguments.end(); ++it)
  306. {
  307. auto target = it->resolveAsFile();
  308. Array<File> files;
  309. if (target.isDirectory())
  310. files = findAllSourceFiles (target);
  311. else
  312. files.add (target);
  313. for (int i = 0; i < files.size(); ++i)
  314. cleanWhitespace (files.getReference (i), options);
  315. }
  316. }
  317. static void cleanWhitespace (const ArgumentList& args, bool replaceTabs)
  318. {
  319. CleanupOptions options = { replaceTabs, false };
  320. scanFilesForCleanup (args, options);
  321. }
  322. static void tidyDividerComments (const ArgumentList& args)
  323. {
  324. CleanupOptions options = { false, true };
  325. scanFilesForCleanup (args, options);
  326. }
  327. //==============================================================================
  328. static File findSimilarlyNamedHeader (const Array<File>& allFiles, const String& name, const File& sourceFile)
  329. {
  330. File result;
  331. for (auto& f : allFiles)
  332. {
  333. if (f.getFileName().equalsIgnoreCase (name) && f != sourceFile)
  334. {
  335. if (result.exists())
  336. return {}; // multiple possible results, so don't change it!
  337. result = f;
  338. }
  339. }
  340. return result;
  341. }
  342. static void fixIncludes (const File& file, const Array<File>& allFiles)
  343. {
  344. const String content (file.loadFileAsString());
  345. StringArray lines;
  346. lines.addLines (content);
  347. bool hasChanged = false;
  348. for (auto& line : lines)
  349. {
  350. if (line.trimStart().startsWith ("#include \""))
  351. {
  352. auto includedFile = line.fromFirstOccurrenceOf ("\"", true, false)
  353. .upToLastOccurrenceOf ("\"", true, false)
  354. .trim()
  355. .unquoted();
  356. auto target = file.getSiblingFile (includedFile);
  357. if (! target.exists())
  358. {
  359. auto header = findSimilarlyNamedHeader (allFiles, target.getFileName(), file);
  360. if (header.exists())
  361. {
  362. line = line.upToFirstOccurrenceOf ("#include \"", true, false)
  363. + header.getRelativePathFrom (file.getParentDirectory())
  364. .replaceCharacter ('\\', '/')
  365. + "\"";
  366. hasChanged = true;
  367. }
  368. }
  369. }
  370. }
  371. if (hasChanged)
  372. {
  373. auto newText = joinLinesIntoSourceFile (lines);
  374. if (newText != content && newText != content + getPreferredLineFeed())
  375. replaceFile (file, newText, "Fixing includes in: ");
  376. }
  377. }
  378. static void fixRelativeIncludePaths (const ArgumentList& args)
  379. {
  380. args.checkMinNumArguments (2);
  381. auto target = args[1].resolveAsExistingFolder();
  382. auto files = findAllSourceFiles (target);
  383. for (int i = 0; i < files.size(); ++i)
  384. fixIncludes (files.getReference(i), files);
  385. }
  386. //==============================================================================
  387. static String getStringConcatenationExpression (Random& rng, int start, int length)
  388. {
  389. jassert (length > 0);
  390. if (length == 1)
  391. return "s" + String (start);
  392. int breakPos = jlimit (1, length - 1, (length / 3) + rng.nextInt (jmax (1, length / 3)));
  393. return "(" + getStringConcatenationExpression (rng, start, breakPos)
  394. + " + " + getStringConcatenationExpression (rng, start + breakPos, length - breakPos) + ")";
  395. }
  396. static void generateObfuscatedStringCode (const ArgumentList& args)
  397. {
  398. args.checkMinNumArguments (2);
  399. auto originalText = args[1].text.unquoted();
  400. struct Section
  401. {
  402. String text;
  403. int position, index;
  404. void writeGenerator (MemoryOutputStream& out) const
  405. {
  406. String name ("s" + String (index));
  407. out << " String " << name << "; " << name;
  408. auto escapeIfSingleQuote = [] (const String& s) -> String
  409. {
  410. if (s == "\'")
  411. return "\\'";
  412. return s;
  413. };
  414. for (int i = 0; i < text.length(); ++i)
  415. out << " << '" << escapeIfSingleQuote (String::charToString (text[i])) << "'";
  416. out << ";" << preferredLineFeed;
  417. }
  418. };
  419. Array<Section> sections;
  420. String text = originalText;
  421. Random rng;
  422. while (text.isNotEmpty())
  423. {
  424. int pos = jmax (0, text.length() - (1 + rng.nextInt (6)));
  425. Section s = { text.substring (pos), pos, 0 };
  426. sections.insert (0, s);
  427. text = text.substring (0, pos);
  428. }
  429. for (int i = 0; i < sections.size(); ++i)
  430. sections.getReference(i).index = i;
  431. for (int i = 0; i < sections.size(); ++i)
  432. sections.swap (i, rng.nextInt (sections.size()));
  433. MemoryOutputStream out;
  434. out << "String createString()" << preferredLineFeed
  435. << "{" << preferredLineFeed;
  436. for (int i = 0; i < sections.size(); ++i)
  437. sections.getReference(i).writeGenerator (out);
  438. out << preferredLineFeed
  439. << " String result = " << getStringConcatenationExpression (rng, 0, sections.size()) << ";" << preferredLineFeed
  440. << preferredLineFeed
  441. << " jassert (result == " << originalText.quoted() << ");" << preferredLineFeed
  442. << " return result;" << preferredLineFeed
  443. << "}" << preferredLineFeed;
  444. std::cout << out.toString() << std::endl;
  445. }
  446. static void scanFoldersForTranslationFiles (const ArgumentList& args)
  447. {
  448. args.checkMinNumArguments (2);
  449. StringArray translations;
  450. for (auto it = args.arguments.begin() + 1; it != args.arguments.end(); ++it)
  451. {
  452. auto directoryToSearch = it->resolveAsExistingFolder();
  453. TranslationHelpers::scanFolderForTranslations (translations, directoryToSearch);
  454. }
  455. std::cout << TranslationHelpers::mungeStrings (translations) << std::endl;
  456. }
  457. static void createFinishedTranslationFile (const ArgumentList& args)
  458. {
  459. args.checkMinNumArguments (3);
  460. auto preTranslated = args[1].resolveAsExistingFile().loadFileAsString();
  461. auto postTranslated = args[2].resolveAsExistingFile().loadFileAsString();
  462. auto localisedContent = (args.size() > 3 ? args[3].resolveAsExistingFile().loadFileAsString() : String());
  463. auto localised = LocalisedStrings (localisedContent, false);
  464. using TH = TranslationHelpers;
  465. std::cout << TH::createFinishedTranslationFile (TH::withTrimmedEnds (TH::breakApart (preTranslated)),
  466. TH::withTrimmedEnds (TH::breakApart (postTranslated)),
  467. localised) << std::endl;
  468. }
  469. //==============================================================================
  470. static void encodeBinary (const ArgumentList& args)
  471. {
  472. args.checkMinNumArguments (3);
  473. auto source = args[1].resolveAsExistingFile();
  474. auto target = args[2].resolveAsExistingFile();
  475. MemoryOutputStream literal;
  476. size_t dataSize = 0;
  477. {
  478. MemoryBlock data;
  479. FileInputStream input (source);
  480. input.readIntoMemoryBlock (data);
  481. build_tools::writeDataAsCppLiteral (data, literal, true, true);
  482. dataSize = data.getSize();
  483. }
  484. auto variableName = build_tools::makeBinaryDataIdentifierName (source);
  485. MemoryOutputStream header, cpp;
  486. header << "// Auto-generated binary data by the Projucer" << preferredLineFeed
  487. << "// Source file: " << source.getRelativePathFrom (target.getParentDirectory()) << preferredLineFeed
  488. << preferredLineFeed;
  489. cpp << header.toString();
  490. if (target.hasFileExtension (headerFileExtensions))
  491. {
  492. header << "static constexpr unsigned char " << variableName << "[] =" << preferredLineFeed
  493. << literal.toString() << preferredLineFeed
  494. << preferredLineFeed;
  495. replaceFile (target, header.toString(), "Writing: ");
  496. }
  497. else if (target.hasFileExtension (cppFileExtensions))
  498. {
  499. header << "extern const char* " << variableName << ";" << preferredLineFeed
  500. << "const unsigned int " << variableName << "Size = " << (int) dataSize << ";" << preferredLineFeed
  501. << preferredLineFeed;
  502. cpp << CodeHelpers::createIncludeStatement (target.withFileExtension (".h").getFileName()) << preferredLineFeed
  503. << preferredLineFeed
  504. << "static constexpr unsigned char " << variableName << "_local[] =" << preferredLineFeed
  505. << literal.toString() << preferredLineFeed
  506. << preferredLineFeed
  507. << "const char* " << variableName << " = (const char*) " << variableName << "_local;" << preferredLineFeed;
  508. replaceFile (target, cpp.toString(), "Writing: ");
  509. replaceFile (target.withFileExtension (".h"), header.toString(), "Writing: ");
  510. }
  511. else
  512. {
  513. ConsoleApplication::fail ("You need to specify a .h or .cpp file as the target");
  514. }
  515. }
  516. //==============================================================================
  517. static bool isThisOS (const String& os)
  518. {
  519. auto targetOS = TargetOS::unknown;
  520. if (os == "osx") targetOS = TargetOS::osx;
  521. else if (os == "windows") targetOS = TargetOS::windows;
  522. else if (os == "linux") targetOS = TargetOS::linux;
  523. if (targetOS == TargetOS::unknown)
  524. ConsoleApplication::fail ("You need to specify a valid OS! Use osx, windows or linux");
  525. return targetOS == TargetOS::getThisOS();
  526. }
  527. static bool isValidPathIdentifier (const String& id, const String& os)
  528. {
  529. return id == "vstLegacyPath" || (id == "aaxPath" && os != "linux") || (id == "rtasPath" && os != "linux")
  530. || id == "androidSDKPath" || id == "androidNDKPath" || id == "defaultJuceModulePath" || id == "defaultUserModulePath";
  531. }
  532. static void setGlobalPath (const ArgumentList& args)
  533. {
  534. args.checkMinNumArguments (3);
  535. if (! isValidPathIdentifier (args[2].text, args[1].text))
  536. ConsoleApplication::fail ("Identifier " + args[2].text + " is not valid for the OS " + args[1].text);
  537. auto userAppData = File::getSpecialLocation (File::userApplicationDataDirectory);
  538. #if JUCE_MAC
  539. userAppData = userAppData.getChildFile ("Application Support");
  540. #endif
  541. auto settingsFile = userAppData.getChildFile ("Projucer").getChildFile ("Projucer.settings");
  542. auto xml = parseXML (settingsFile);
  543. if (xml == nullptr)
  544. ConsoleApplication::fail ("Settings file not valid!");
  545. auto settingsTree = ValueTree::fromXml (*xml);
  546. if (! settingsTree.isValid())
  547. ConsoleApplication::fail ("Settings file not valid!");
  548. ValueTree childToSet;
  549. if (isThisOS (args[1].text))
  550. {
  551. childToSet = settingsTree.getChildWithProperty (Ids::name, "PROJECT_DEFAULT_SETTINGS")
  552. .getOrCreateChildWithName ("PROJECT_DEFAULT_SETTINGS", nullptr);
  553. }
  554. else
  555. {
  556. childToSet = settingsTree.getChildWithProperty (Ids::name, "FALLBACK_PATHS")
  557. .getOrCreateChildWithName ("FALLBACK_PATHS", nullptr)
  558. .getOrCreateChildWithName (args[1].text + "Fallback", nullptr);
  559. }
  560. if (! childToSet.isValid())
  561. ConsoleApplication::fail ("Failed to set the requested setting!");
  562. childToSet.setProperty (args[2].text, args[3].resolveAsFile().getFullPathName(), nullptr);
  563. settingsFile.replaceWithText (settingsTree.toXmlString());
  564. }
  565. static void createProjectFromPIP (const ArgumentList& args)
  566. {
  567. args.checkMinNumArguments (3);
  568. auto pipFile = args[1].resolveAsFile();
  569. if (! pipFile.existsAsFile())
  570. ConsoleApplication::fail ("PIP file doesn't exist.");
  571. auto outputDir = args[2].resolveAsFile();
  572. if (! outputDir.exists())
  573. {
  574. auto res = outputDir.createDirectory();
  575. std::cout << "Creating directory " << outputDir.getFullPathName() << std::endl;
  576. }
  577. File juceModulesPath, userModulesPath;
  578. if (args.size() > 3)
  579. {
  580. juceModulesPath = args[3].resolveAsFile();
  581. if (! juceModulesPath.exists())
  582. ConsoleApplication::fail ("Specified JUCE modules directory doesn't exist.");
  583. if (args.size() == 5)
  584. {
  585. userModulesPath = args[4].resolveAsFile();
  586. if (! userModulesPath.exists())
  587. ConsoleApplication::fail ("Specified JUCE modules directory doesn't exist.");
  588. }
  589. }
  590. PIPGenerator generator (pipFile, outputDir, juceModulesPath, userModulesPath);
  591. auto createJucerFileResult = generator.createJucerFile();
  592. if (! createJucerFileResult)
  593. ConsoleApplication::fail (createJucerFileResult.getErrorMessage());
  594. auto createMainCppResult = generator.createMainCpp();
  595. if (! createMainCppResult)
  596. ConsoleApplication::fail (createMainCppResult.getErrorMessage());
  597. }
  598. //==============================================================================
  599. static void showHelp()
  600. {
  601. hideDockIcon();
  602. auto appName = JUCEApplication::getInstance()->getApplicationName();
  603. std::cout << appName << std::endl
  604. << std::endl
  605. << "Usage: " << std::endl
  606. << std::endl
  607. << " " << appName << " --resave project_file" << std::endl
  608. << " Resaves all files and resources in a project." << std::endl
  609. << std::endl
  610. << " " << appName << " --resave-resources project_file" << std::endl
  611. << " Resaves just the binary resources for a project." << std::endl
  612. << std::endl
  613. << " " << appName << " --get-version project_file" << std::endl
  614. << " Returns the version number of a project." << std::endl
  615. << std::endl
  616. << " " << appName << " --set-version version_number project_file" << std::endl
  617. << " Updates the version number in a project." << std::endl
  618. << std::endl
  619. << " " << appName << " --bump-version project_file" << std::endl
  620. << " Updates the minor version number in a project by 1." << std::endl
  621. << std::endl
  622. << " " << appName << " --git-tag-version project_file" << std::endl
  623. << " Invokes 'git tag' to attach the project's version number to the current git repository." << std::endl
  624. << std::endl
  625. << " " << appName << " --status project_file" << std::endl
  626. << " Displays information about a project." << std::endl
  627. << std::endl
  628. << " " << appName << " --buildmodule target_folder module_folder" << std::endl
  629. << " Zips a module into a downloadable file format." << std::endl
  630. << std::endl
  631. << " " << appName << " --buildallmodules target_folder module_folder" << std::endl
  632. << " Zips all modules in a given folder and creates an index for them." << std::endl
  633. << std::endl
  634. << " " << appName << " --trim-whitespace target_folder" << std::endl
  635. << " Scans the given folder for C/C++ source files (recursively), and trims any trailing whitespace from their lines, as well as normalising their line-endings to CR-LF." << std::endl
  636. << std::endl
  637. << " " << appName << " --remove-tabs target_folder" << std::endl
  638. << " Scans the given folder for C/C++ source files (recursively), and replaces any tab characters with 4 spaces." << std::endl
  639. << std::endl
  640. << " " << appName << " --tidy-divider-comments target_folder" << std::endl
  641. << " Scans the given folder for C/C++ source files (recursively), and normalises any juce-style comment division lines (i.e. any lines that look like //===== or //------- or /////////// will be replaced)." << std::endl
  642. << std::endl
  643. << " " << appName << " --fix-broken-include-paths target_folder" << std::endl
  644. << " Scans the given folder for C/C++ source files (recursively). Where a file contains an #include of one of the other filenames, it changes it to use the optimum relative path. Helpful for auto-fixing includes when re-arranging files and folders in a project." << std::endl
  645. << std::endl
  646. << " " << appName << " --obfuscated-string-code string_to_obfuscate" << std::endl
  647. << " Generates a C++ function which returns the given string, but in an obfuscated way." << std::endl
  648. << std::endl
  649. << " " << appName << " --encode-binary source_binary_file target_cpp_file" << std::endl
  650. << " Converts a binary file to a C++ file containing its contents as a block of data. Provide a .h file as the target if you want a single output file, or a .cpp file if you want a pair of .h/.cpp files." << std::endl
  651. << std::endl
  652. << " " << appName << " --trans target_folders..." << std::endl
  653. << " Scans each of the given folders (recursively) for any NEEDS_TRANS macros, and generates a translation file that can be used with Projucer's translation file builder" << std::endl
  654. << std::endl
  655. << " " << appName << " --trans-finish pre_translated_file post_translated_file optional_existing_translation_file" << std::endl
  656. << " Creates a completed translations mapping file, that can be used to initialise a LocalisedStrings object. This allows you to localise the strings in your project" << std::endl
  657. << std::endl
  658. << " " << appName << " --set-global-search-path os identifier_to_set new_path" << std::endl
  659. << " Sets the global path for a specified os and identifier. The os should be either osx, windows or linux and the identifiers can be any of the following: "
  660. << "defaultJuceModulePath, defaultUserModulePath, vstLegacyPath, aaxPath (not valid on linux), rtasPath (not valid on linux), androidSDKPath or androidNDKPath. " << std::endl
  661. << std::endl
  662. << " " << appName << " --create-project-from-pip path/to/PIP path/to/output path/to/JUCE/modules (optional) path/to/user/modules (optional)" << std::endl
  663. << " Generates a folder containing a JUCE project in the specified output path using the specified PIP file. Use the optional JUCE and user module paths to override "
  664. "the global module paths." << std::endl
  665. << std::endl
  666. << "Note that for any of the file-rewriting commands, add the option \"--lf\" if you want it to use LF linefeeds instead of CRLF" << std::endl
  667. << std::endl;
  668. }
  669. }
  670. //==============================================================================
  671. int performCommandLine (const ArgumentList& args)
  672. {
  673. return ConsoleApplication::invokeCatchingFailures ([&] () -> int
  674. {
  675. if (args.containsOption ("--lf"))
  676. preferredLineFeed = "\n";
  677. auto command = args[0];
  678. auto matchCommand = [&] (StringRef name) -> bool
  679. {
  680. return command == name || command.isLongOption (name);
  681. };
  682. if (matchCommand ("help")) { showHelp(); return 0; }
  683. if (matchCommand ("h")) { showHelp(); return 0; }
  684. if (matchCommand ("resave")) { resaveProject (args, false); return 0; }
  685. if (matchCommand ("resave-resources")) { resaveProject (args, true); return 0; }
  686. if (matchCommand ("get-version")) { getVersion (args); return 0; }
  687. if (matchCommand ("set-version")) { setVersion (args); return 0; }
  688. if (matchCommand ("bump-version")) { bumpVersion (args); return 0; }
  689. if (matchCommand ("git-tag-version")) { gitTag (args); return 0; }
  690. if (matchCommand ("buildmodule")) { buildModules (args, false); return 0; }
  691. if (matchCommand ("buildallmodules")) { buildModules (args, true); return 0; }
  692. if (matchCommand ("status")) { showStatus (args); return 0; }
  693. if (matchCommand ("trim-whitespace")) { cleanWhitespace (args, false); return 0; }
  694. if (matchCommand ("remove-tabs")) { cleanWhitespace (args, true); return 0; }
  695. if (matchCommand ("tidy-divider-comments")) { tidyDividerComments (args); return 0; }
  696. if (matchCommand ("fix-broken-include-paths")) { fixRelativeIncludePaths (args); return 0; }
  697. if (matchCommand ("obfuscated-string-code")) { generateObfuscatedStringCode (args); return 0; }
  698. if (matchCommand ("encode-binary")) { encodeBinary (args); return 0; }
  699. if (matchCommand ("trans")) { scanFoldersForTranslationFiles (args); return 0; }
  700. if (matchCommand ("trans-finish")) { createFinishedTranslationFile (args); return 0; }
  701. if (matchCommand ("set-global-search-path")) { setGlobalPath (args); return 0; }
  702. if (matchCommand ("create-project-from-pip")) { createProjectFromPIP (args); return 0; }
  703. if (command.isLongOption() || command.isShortOption())
  704. ConsoleApplication::fail ("Unrecognised command: " + command.text.quoted());
  705. return commandLineNotPerformed;
  706. });
  707. }