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.

535 lines
18KB

  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. static bool cleanWhitespace (const File& file, bool replaceTabs)
  278. {
  279. const String content (file.loadFileAsString());
  280. if (content.contains ("%%") && content.contains ("//["))
  281. return true; // ignore introjucer GUI template files
  282. StringArray lines;
  283. lines.addLines (content);
  284. bool anyTabsRemoved = false;
  285. for (int i = 0; i < lines.size(); ++i)
  286. {
  287. String& line = lines.getReference(i);
  288. if (replaceTabs && line.containsChar ('\t'))
  289. {
  290. anyTabsRemoved = true;
  291. for (;;)
  292. {
  293. const int tabPos = line.indexOfChar ('\t');
  294. if (tabPos < 0)
  295. break;
  296. const int spacesPerTab = 4;
  297. const int spacesNeeded = spacesPerTab - (tabPos % spacesPerTab);
  298. line = line.replaceSection (tabPos, 1, String::repeatedString (" ", spacesNeeded));
  299. }
  300. }
  301. line = line.trimEnd();
  302. }
  303. if (replaceTabs && ! anyTabsRemoved)
  304. return true;
  305. while (lines.size() > 10 && lines [lines.size() - 1].isEmpty())
  306. lines.remove (lines.size() - 1);
  307. const char* lineEnding = "\r\n";
  308. const String newText (lines.joinIntoString (lineEnding) + lineEnding);
  309. if (newText == content || newText == content + lineEnding)
  310. return true;
  311. std::cout << (replaceTabs ? "Removing tabs in: "
  312. : "Cleaning file: ") << file.getFullPathName() << std::endl;
  313. TemporaryFile temp (file);
  314. if (! temp.getFile().replaceWithText (newText, false, false))
  315. {
  316. std::cout << "!!! ERROR Couldn't write to temp file!" << std::endl << std::endl;
  317. return false;
  318. }
  319. if (! temp.overwriteTargetFileWithTemporary())
  320. {
  321. std::cout << "!!! ERROR Couldn't write to file!" << std::endl << std::endl;
  322. return false;
  323. }
  324. return true;
  325. }
  326. static int cleanWhitespace (const StringArray& args, bool replaceTabs)
  327. {
  328. if (! checkArgumentCount (args, 2))
  329. return 1;
  330. const File targetFolder (getFile (args[1]));
  331. if (! targetFolder.exists())
  332. {
  333. std::cout << "Could not find folder: " << args[1] << std::endl;
  334. return 1;
  335. }
  336. if (targetFolder.isDirectory())
  337. {
  338. for (DirectoryIterator di (targetFolder, true, "*.cpp;*.h;*.hpp;*.c;*.cc;*.mm;*.m", File::findFiles); di.next();)
  339. if (! cleanWhitespace (di.getFile(), replaceTabs))
  340. return 1;
  341. }
  342. else
  343. {
  344. if (! cleanWhitespace (targetFolder, replaceTabs))
  345. return 1;
  346. }
  347. return 0;
  348. }
  349. //==============================================================================
  350. static int showHelp()
  351. {
  352. hideDockIcon();
  353. std::cout << "The Introjucer!" << std::endl
  354. << std::endl
  355. << "Usage: " << std::endl
  356. << std::endl
  357. << " introjucer --resave project_file" << std::endl
  358. << " Resaves all files and resources in a project." << std::endl
  359. << std::endl
  360. << " introjucer --resave-resources project_file" << std::endl
  361. << " Resaves just the binary resources for a project." << std::endl
  362. << std::endl
  363. << " introjucer --set-version version_number project_file" << std::endl
  364. << " Updates the version number in a project." << std::endl
  365. << std::endl
  366. << " introjucer --bump-version project_file" << std::endl
  367. << " Updates the minor version number in a project by 1." << std::endl
  368. << std::endl
  369. << " introjucer --git-tag-version project_file" << std::endl
  370. << " Invokes 'git tag' to attach the project's version number to the current git repository." << std::endl
  371. << std::endl
  372. << " introjucer --status project_file" << std::endl
  373. << " Displays information about a project." << std::endl
  374. << std::endl
  375. << " introjucer --buildmodule target_folder module_folder" << std::endl
  376. << " Zips a module into a downloadable file format." << std::endl
  377. << std::endl
  378. << " introjucer --buildallmodules target_folder module_folder" << std::endl
  379. << " Zips all modules in a given folder and creates an index for them." << std::endl
  380. << std::endl
  381. << " introjucer --trim-whitespace target_folder" << std::endl
  382. << " 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
  383. << std::endl
  384. << " introjucer --remove-tabs target_folder" << std::endl
  385. << " Scans the given folder for C/C++ source files, and replaces any tab characters with 4 spaces." << std::endl
  386. << std::endl;
  387. return 0;
  388. }
  389. }
  390. //==============================================================================
  391. int performCommandLine (const String& commandLine)
  392. {
  393. StringArray args;
  394. args.addTokens (commandLine, true);
  395. args.trim();
  396. String command (args[0]);
  397. if (matchArgument (command, "help")) return showHelp();
  398. if (matchArgument (command, "h")) return showHelp();
  399. if (matchArgument (command, "resave")) return resaveProject (args, false);
  400. if (matchArgument (command, "resave-resources")) return resaveProject (args, true);
  401. if (matchArgument (command, "set-version")) return setVersion (args);
  402. if (matchArgument (command, "bump-version")) return bumpVersion (args);
  403. if (matchArgument (command, "git-tag-version")) return gitTag (args);
  404. if (matchArgument (command, "buildmodule")) return buildModules (args, false);
  405. if (matchArgument (command, "buildallmodules")) return buildModules (args, true);
  406. if (matchArgument (command, "status")) return showStatus (args);
  407. if (matchArgument (command, "trim-whitespace")) return cleanWhitespace (args, false);
  408. if (matchArgument (command, "remove-tabs")) return cleanWhitespace (args, true);
  409. return commandLineNotPerformed;
  410. }