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.

577 lines
20KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2015 - ROLI Ltd.
  5. Permission is granted to use this software under the terms of either:
  6. a) the GPL v2 (or any later version)
  7. b) the Affero GPL v3
  8. Details of these licenses can be found at: www.gnu.org/licenses
  9. JUCE is distributed in the hope that it will be useful, but WITHOUT ANY
  10. WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR
  11. A PARTICULAR PURPOSE. See the GNU General Public License for more details.
  12. ------------------------------------------------------------------------------
  13. To release a closed-source product which uses JUCE, commercial licenses are
  14. available: visit www.juce.com for more information.
  15. ==============================================================================
  16. */
  17. #include "../Project/jucer_Project.h"
  18. #include "../Project/jucer_Module.h"
  19. #include "jucer_CommandLine.h"
  20. //==============================================================================
  21. namespace
  22. {
  23. static void hideDockIcon()
  24. {
  25. #if JUCE_MAC
  26. Process::setDockIconVisible (false);
  27. #endif
  28. }
  29. static File getFile (const String& filename)
  30. {
  31. return File::getCurrentWorkingDirectory().getChildFile (filename.unquoted());
  32. }
  33. static bool matchArgument (const String& arg, const String& possible)
  34. {
  35. return arg == possible
  36. || arg == "-" + possible
  37. || arg == "--" + possible;
  38. }
  39. static bool checkArgumentCount (const StringArray& args, int minNumArgs)
  40. {
  41. if (args.size() < minNumArgs)
  42. {
  43. std::cout << "Not enough arguments!" << std::endl;
  44. return false;
  45. }
  46. return true;
  47. }
  48. //==============================================================================
  49. struct LoadedProject
  50. {
  51. LoadedProject()
  52. {
  53. hideDockIcon();
  54. }
  55. int load (const File& projectFile)
  56. {
  57. hideDockIcon();
  58. if (! projectFile.exists())
  59. {
  60. std::cout << "The file " << projectFile.getFullPathName() << " doesn't exist!" << std::endl;
  61. return 1;
  62. }
  63. if (! projectFile.hasFileExtension (Project::projectFileExtension))
  64. {
  65. std::cout << projectFile.getFullPathName() << " isn't a valid jucer project file!" << std::endl;
  66. return 1;
  67. }
  68. project = new Project (projectFile);
  69. if (! project->loadFrom (projectFile, true))
  70. {
  71. project = nullptr;
  72. std::cout << "Failed to load the project file: " << projectFile.getFullPathName() << std::endl;
  73. return 1;
  74. }
  75. return 0;
  76. }
  77. int save (bool justSaveResources)
  78. {
  79. if (project != nullptr)
  80. {
  81. Result error (justSaveResources ? project->saveResourcesOnly (project->getFile())
  82. : project->saveProject (project->getFile(), true));
  83. if (error.failed())
  84. {
  85. std::cout << "Error when saving: " << error.getErrorMessage() << std::endl;
  86. return 1;
  87. }
  88. project = nullptr;
  89. }
  90. return 0;
  91. }
  92. ScopedPointer<Project> project;
  93. };
  94. //==============================================================================
  95. /* Running a command-line of the form "introjucer --resave foobar.jucer" will try to load
  96. that project and re-export all of its targets.
  97. */
  98. static int resaveProject (const StringArray& args, bool justSaveResources)
  99. {
  100. if (! checkArgumentCount (args, 2))
  101. return 1;
  102. LoadedProject proj;
  103. int res = proj.load (getFile (args[1]));
  104. if (res != 0)
  105. return res;
  106. std::cout << (justSaveResources ? "Re-saving project resources: "
  107. : "Re-saving file: ")
  108. << proj.project->getFile().getFullPathName() << std::endl;
  109. return proj.save (justSaveResources);
  110. }
  111. //==============================================================================
  112. static int setVersion (const StringArray& args)
  113. {
  114. if (! checkArgumentCount (args, 3))
  115. return 1;
  116. LoadedProject proj;
  117. int res = proj.load (getFile (args[2]));
  118. if (res != 0)
  119. return res;
  120. String version (args[1].trim());
  121. std::cout << "Setting project version: " << version << std::endl;
  122. proj.project->getVersionValue() = version;
  123. return proj.save (false);
  124. }
  125. //==============================================================================
  126. static int bumpVersion (const StringArray& args)
  127. {
  128. if (! checkArgumentCount (args, 2))
  129. return 1;
  130. LoadedProject proj;
  131. int res = proj.load (getFile (args[1]));
  132. if (res != 0)
  133. return res;
  134. String version = proj.project->getVersionString();
  135. version = version.upToLastOccurrenceOf (".", true, false)
  136. + String (version.getTrailingIntValue() + 1);
  137. std::cout << "Bumping project version to: " << version << std::endl;
  138. proj.project->getVersionValue() = version;
  139. return proj.save (false);
  140. }
  141. static int gitTag (const StringArray& args)
  142. {
  143. if (! checkArgumentCount (args, 2))
  144. return 1;
  145. LoadedProject proj;
  146. int res = proj.load (getFile (args[1]));
  147. if (res != 0)
  148. return res;
  149. String version (proj.project->getVersionValue().toString());
  150. if (version.trim().isEmpty())
  151. {
  152. std::cout << "Cannot read version number from project!" << std::endl;
  153. return 1;
  154. }
  155. StringArray command;
  156. command.add ("git");
  157. command.add ("tag");
  158. command.add ("-a");
  159. command.add (version);
  160. command.add ("-m");
  161. command.add (version.quoted());
  162. std::cout << "Performing command: " << command.joinIntoString(" ") << std::endl;
  163. ChildProcess c;
  164. if (! c.start (command, 0))
  165. {
  166. std::cout << "Cannot run git!" << std::endl;
  167. return 1;
  168. }
  169. c.waitForProcessToFinish (10000);
  170. return (int) c.getExitCode();
  171. }
  172. //==============================================================================
  173. static int showStatus (const StringArray& args)
  174. {
  175. hideDockIcon();
  176. if (! checkArgumentCount (args, 2))
  177. return 1;
  178. LoadedProject proj;
  179. int res = proj.load (getFile (args[1]));
  180. if (res != 0)
  181. return res;
  182. std::cout << "Project file: " << proj.project->getFile().getFullPathName() << std::endl
  183. << "Name: " << proj.project->getTitle() << std::endl
  184. << "UID: " << proj.project->getProjectUID() << std::endl;
  185. EnabledModuleList& modules = proj.project->getModules();
  186. const int numModules = modules.getNumModules();
  187. if (numModules > 0)
  188. {
  189. std::cout << "Modules:" << std::endl;
  190. for (int i = 0; i < numModules; ++i)
  191. std::cout << " " << modules.getModuleID (i) << std::endl;
  192. }
  193. return 0;
  194. }
  195. //==============================================================================
  196. static String getModulePackageName (const LibraryModule& module)
  197. {
  198. return module.getID() + ".jucemodule";
  199. }
  200. static int zipModule (const File& targetFolder, const File& moduleFolder)
  201. {
  202. jassert (targetFolder.isDirectory());
  203. const File moduleFolderParent (moduleFolder.getParentDirectory());
  204. LibraryModule module (moduleFolder.getChildFile (ModuleDescription::getManifestFileName()));
  205. if (! module.isValid())
  206. {
  207. std::cout << moduleFolder.getFullPathName() << " is not a valid module folder!" << std::endl;
  208. return 1;
  209. }
  210. const File targetFile (targetFolder.getChildFile (getModulePackageName (module)));
  211. ZipFile::Builder zip;
  212. {
  213. DirectoryIterator i (moduleFolder, true, "*", File::findFiles);
  214. while (i.next())
  215. if (! i.getFile().isHidden())
  216. zip.addFile (i.getFile(), 9, i.getFile().getRelativePathFrom (moduleFolderParent));
  217. }
  218. std::cout << "Writing: " << targetFile.getFullPathName() << std::endl;
  219. TemporaryFile temp (targetFile);
  220. ScopedPointer<FileOutputStream> out (temp.getFile().createOutputStream());
  221. bool ok = out != nullptr && zip.writeToStream (*out, nullptr);
  222. out = nullptr;
  223. ok = ok && temp.overwriteTargetFileWithTemporary();
  224. if (! ok)
  225. {
  226. std::cout << "Failed to write to the target file: " << targetFile.getFullPathName() << std::endl;
  227. return 1;
  228. }
  229. return 0;
  230. }
  231. static int buildModules (const StringArray& args, const bool buildAllWithIndex)
  232. {
  233. hideDockIcon();
  234. if (! checkArgumentCount (args, 3))
  235. return 1;
  236. const File targetFolder (getFile (args[1]));
  237. if (! targetFolder.isDirectory())
  238. {
  239. std::cout << "The first argument must be the directory to put the result." << std::endl;
  240. return 1;
  241. }
  242. if (buildAllWithIndex)
  243. {
  244. const File folderToSearch (getFile (args[2]));
  245. DirectoryIterator i (folderToSearch, false, "*", File::findDirectories);
  246. var infoList;
  247. while (i.next())
  248. {
  249. LibraryModule module (i.getFile().getChildFile (ModuleDescription::getManifestFileName()));
  250. if (module.isValid())
  251. {
  252. const int result = zipModule (targetFolder, i.getFile());
  253. if (result != 0)
  254. return result;
  255. var moduleInfo (new DynamicObject());
  256. moduleInfo.getDynamicObject()->setProperty ("file", getModulePackageName (module));
  257. moduleInfo.getDynamicObject()->setProperty ("info", module.moduleInfo.moduleInfo);
  258. infoList.append (moduleInfo);
  259. }
  260. }
  261. const File indexFile (targetFolder.getChildFile ("modulelist"));
  262. std::cout << "Writing: " << indexFile.getFullPathName() << std::endl;
  263. indexFile.replaceWithText (JSON::toString (infoList), false, false);
  264. }
  265. else
  266. {
  267. for (int i = 2; i < args.size(); ++i)
  268. {
  269. const int result = zipModule (targetFolder, getFile (args[i]));
  270. if (result != 0)
  271. return result;
  272. }
  273. }
  274. return 0;
  275. }
  276. //==============================================================================
  277. struct CleanupOptions
  278. {
  279. bool removeTabs;
  280. bool fixDividerComments;
  281. };
  282. static bool cleanWhitespace (const File& file, CleanupOptions options)
  283. {
  284. const String content (file.loadFileAsString());
  285. if (content.contains ("%%") && content.contains ("//["))
  286. return true; // ignore introjucer GUI template files
  287. StringArray lines;
  288. lines.addLines (content);
  289. bool anyTabsRemoved = false;
  290. for (int i = 0; i < lines.size(); ++i)
  291. {
  292. String& line = lines.getReference(i);
  293. if (options.removeTabs && line.containsChar ('\t'))
  294. {
  295. anyTabsRemoved = true;
  296. for (;;)
  297. {
  298. const int tabPos = line.indexOfChar ('\t');
  299. if (tabPos < 0)
  300. break;
  301. const int spacesPerTab = 4;
  302. const int spacesNeeded = spacesPerTab - (tabPos % spacesPerTab);
  303. line = line.replaceSection (tabPos, 1, String::repeatedString (" ", spacesNeeded));
  304. }
  305. }
  306. if (options.fixDividerComments)
  307. {
  308. String afterIndent (line.trim());
  309. if (afterIndent.startsWith ("//") && afterIndent.length() > 20)
  310. {
  311. afterIndent = afterIndent.substring (2);
  312. if (afterIndent.containsOnly ("=")
  313. || afterIndent.containsOnly ("/")
  314. || afterIndent.containsOnly ("-"))
  315. {
  316. line = line.substring (0, line.indexOfChar ('/'))
  317. + "//" + String::repeatedString ("=", 78);
  318. }
  319. }
  320. }
  321. line = line.trimEnd();
  322. }
  323. if (options.removeTabs && ! anyTabsRemoved)
  324. return true;
  325. while (lines.size() > 10 && lines [lines.size() - 1].isEmpty())
  326. lines.remove (lines.size() - 1);
  327. const char* lineEnding = "\r\n";
  328. const String newText (lines.joinIntoString (lineEnding) + lineEnding);
  329. if (newText == content || newText == content + lineEnding)
  330. return true;
  331. std::cout << (options.removeTabs ? "Removing tabs in: "
  332. : "Cleaning file: ") << file.getFullPathName() << std::endl;
  333. TemporaryFile temp (file);
  334. if (! temp.getFile().replaceWithText (newText, false, false))
  335. {
  336. std::cout << "!!! ERROR Couldn't write to temp file!" << std::endl << std::endl;
  337. return false;
  338. }
  339. if (! temp.overwriteTargetFileWithTemporary())
  340. {
  341. std::cout << "!!! ERROR Couldn't write to file!" << std::endl << std::endl;
  342. return false;
  343. }
  344. return true;
  345. }
  346. static int scanFilesForCleanup (const StringArray& args, CleanupOptions options)
  347. {
  348. if (! checkArgumentCount (args, 2))
  349. return 1;
  350. const File targetFolder (getFile (args[1]));
  351. if (! targetFolder.exists())
  352. {
  353. std::cout << "Could not find folder: " << args[1] << std::endl;
  354. return 1;
  355. }
  356. if (targetFolder.isDirectory())
  357. {
  358. for (DirectoryIterator di (targetFolder, true, "*.cpp;*.h;*.hpp;*.c;*.cc;*.mm;*.m", File::findFiles); di.next();)
  359. if (! cleanWhitespace (di.getFile(), options))
  360. return 1;
  361. }
  362. else
  363. {
  364. if (! cleanWhitespace (targetFolder, options))
  365. return 1;
  366. }
  367. return 0;
  368. }
  369. static int cleanWhitespace (const StringArray& args, bool replaceTabs)
  370. {
  371. CleanupOptions options = { replaceTabs, false };
  372. return scanFilesForCleanup (args, options);
  373. }
  374. static int tidyDividerComments (const StringArray& args)
  375. {
  376. CleanupOptions options = { false, true };
  377. return scanFilesForCleanup (args, options);
  378. }
  379. //==============================================================================
  380. static int showHelp()
  381. {
  382. hideDockIcon();
  383. const String appName (JUCEApplication::getInstance()->getApplicationName());
  384. std::cout << appName << std::endl
  385. << std::endl
  386. << "Usage: " << std::endl
  387. << std::endl
  388. << " " << appName << " --resave project_file" << std::endl
  389. << " Resaves all files and resources in a project." << std::endl
  390. << std::endl
  391. << " " << appName << " --resave-resources project_file" << std::endl
  392. << " Resaves just the binary resources for a project." << std::endl
  393. << std::endl
  394. << " " << appName << " --set-version version_number project_file" << std::endl
  395. << " Updates the version number in a project." << std::endl
  396. << std::endl
  397. << " " << appName << " --bump-version project_file" << std::endl
  398. << " Updates the minor version number in a project by 1." << std::endl
  399. << std::endl
  400. << " " << appName << " --git-tag-version project_file" << std::endl
  401. << " Invokes 'git tag' to attach the project's version number to the current git repository." << std::endl
  402. << std::endl
  403. << " " << appName << " --status project_file" << std::endl
  404. << " Displays information about a project." << std::endl
  405. << std::endl
  406. << " " << appName << " --buildmodule target_folder module_folder" << std::endl
  407. << " Zips a module into a downloadable file format." << std::endl
  408. << std::endl
  409. << " " << appName << " --buildallmodules target_folder module_folder" << std::endl
  410. << " Zips all modules in a given folder and creates an index for them." << std::endl
  411. << std::endl
  412. << " " << appName << " --trim-whitespace target_folder" << std::endl
  413. << " Scans the given folder for C/C++ source files, and trims any trailing whitespace from their lines, as well as normalising their line-endings to CR-LF." << std::endl
  414. << std::endl
  415. << " " << appName << " --remove-tabs target_folder" << std::endl
  416. << " Scans the given folder for C/C++ source files, and replaces any tab characters with 4 spaces." << std::endl
  417. << std::endl
  418. << " " << appName << " --tidy-divider-comments target_folder" << std::endl
  419. << " Scans the given folder for C/C++ source files, and normalises any juce-style comment division lines (i.e. any lines that look like //===== or //------- or /////////// will be replaced)." << std::endl
  420. << std::endl;
  421. return 0;
  422. }
  423. }
  424. //==============================================================================
  425. int performCommandLine (const String& commandLine)
  426. {
  427. StringArray args;
  428. args.addTokens (commandLine, true);
  429. args.trim();
  430. String command (args[0]);
  431. if (matchArgument (command, "help")) return showHelp();
  432. if (matchArgument (command, "h")) return showHelp();
  433. if (matchArgument (command, "resave")) return resaveProject (args, false);
  434. if (matchArgument (command, "resave-resources")) return resaveProject (args, true);
  435. if (matchArgument (command, "set-version")) return setVersion (args);
  436. if (matchArgument (command, "bump-version")) return bumpVersion (args);
  437. if (matchArgument (command, "git-tag-version")) return gitTag (args);
  438. if (matchArgument (command, "buildmodule")) return buildModules (args, false);
  439. if (matchArgument (command, "buildallmodules")) return buildModules (args, true);
  440. if (matchArgument (command, "status")) return showStatus (args);
  441. if (matchArgument (command, "trim-whitespace")) return cleanWhitespace (args, false);
  442. if (matchArgument (command, "remove-tabs")) return cleanWhitespace (args, true);
  443. if (matchArgument (command, "tidy-divider-comments")) return tidyDividerComments (args);
  444. return commandLineNotPerformed;
  445. }