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.

382 lines
12KB

  1. /*
  2. ==============================================================================
  3. This file is part of the JUCE library.
  4. Copyright (c) 2022 - Raw Material Software Limited
  5. JUCE is an open source library subject to commercial or open-source
  6. licensing.
  7. By using JUCE, you agree to the terms of both the JUCE 7 End-User License
  8. Agreement and JUCE Privacy Policy.
  9. End User License Agreement: www.juce.com/juce-7-licence
  10. Privacy Policy: www.juce.com/juce-privacy-policy
  11. Or: You may also use this code under the terms of the GPL v3 (see
  12. www.gnu.org/licenses).
  13. JUCE IS PROVIDED "AS IS" WITHOUT ANY WARRANTY, AND ALL WARRANTIES, WHETHER
  14. EXPRESSED OR IMPLIED, INCLUDING MERCHANTABILITY AND FITNESS FOR PURPOSE, ARE
  15. DISCLAIMED.
  16. ==============================================================================
  17. */
  18. #include <sys/stat.h>
  19. #include <unistd.h>
  20. #include <algorithm>
  21. #include <cstdint>
  22. #include <fstream>
  23. #include <iomanip>
  24. #include <iostream>
  25. #include <optional>
  26. #include <vector>
  27. //==============================================================================
  28. struct FileHelpers
  29. {
  30. static std::string getCurrentWorkingDirectory()
  31. {
  32. std::vector<char> buffer (1024);
  33. while (getcwd (buffer.data(), buffer.size() - 1) == nullptr && errno == ERANGE)
  34. buffer.resize (buffer.size() * 2 / 3);
  35. return { buffer.data() };
  36. }
  37. static bool endsWith (const std::string& s, char c)
  38. {
  39. if (s.length() == 0)
  40. return false;
  41. return *s.rbegin() == c;
  42. }
  43. static std::string appendedPaths (const std::string& first, const std::string& second)
  44. {
  45. return endsWith (first, '/') ? first + second : first + "/" + second;
  46. }
  47. static bool exists (const std::string& path)
  48. {
  49. return ! path.empty() && access (path.c_str(), F_OK) == 0;
  50. }
  51. static bool deleteFile (const std::string& path)
  52. {
  53. if (! exists (path))
  54. return true;
  55. return remove (path.c_str()) == 0;
  56. }
  57. static std::string getFilename (const std::string& path)
  58. {
  59. return { std::find_if (path.rbegin(), path.rend(), [] (auto c) { return c == '/'; }).base(),
  60. path.end() };
  61. }
  62. static bool isDirectory (const std::string& path)
  63. {
  64. #if defined (__FreeBSD__) || defined (__OpenBSD__)
  65. #define JUCE_STAT stat
  66. #else
  67. #define JUCE_STAT stat64
  68. #endif
  69. struct JUCE_STAT info;
  70. return ! path.empty()
  71. && JUCE_STAT (path.c_str(), &info) == 0
  72. && ((info.st_mode & S_IFDIR) != 0);
  73. }
  74. static std::string getParentDirectory (const std::string& path)
  75. {
  76. std::string p { path.begin(),
  77. std::find_if (path.rbegin(),
  78. path.rend(),
  79. [] (auto c) { return c == '/'; }).base() };
  80. // Trim the ending slash, but only if not root
  81. if (endsWith (p, '/') && p.length() > 1)
  82. return { p.begin(), p.end() - 1 };
  83. return p;
  84. }
  85. static bool createDirectory (const std::string& path)
  86. {
  87. if (isDirectory (path))
  88. return true;
  89. const auto parentDir = getParentDirectory (path);
  90. if (path == parentDir)
  91. return false;
  92. if (createDirectory (parentDir))
  93. return mkdir (path.c_str(), 0777) != -1;
  94. return false;
  95. }
  96. };
  97. //==============================================================================
  98. struct StringHelpers
  99. {
  100. static bool isQuoteCharacter (char c)
  101. {
  102. return c == '"' || c == '\'';
  103. }
  104. static std::string unquoted (const std::string& str)
  105. {
  106. if (str.length() == 0 || (! isQuoteCharacter (str[0])))
  107. return str;
  108. return str.substr (1, str.length() - (isQuoteCharacter (str[str.length() - 1]) ? 1 : 0));
  109. }
  110. static void ltrim (std::string& s)
  111. {
  112. s.erase (s.begin(), std::find_if (s.begin(), s.end(), [] (int c) { return ! std::isspace (c); }));
  113. }
  114. static void rtrim (std::string& s)
  115. {
  116. s.erase (std::find_if (s.rbegin(), s.rend(), [] (int c) { return ! std::isspace (c); }).base(), s.end());
  117. }
  118. static std::string trimmed (const std::string& str)
  119. {
  120. auto result = str;
  121. ltrim (result);
  122. rtrim (result);
  123. return result;
  124. }
  125. static std::string replaced (const std::string& str, char charToReplace, char replaceWith)
  126. {
  127. auto result = str;
  128. std::replace (result.begin(), result.end(), charToReplace, replaceWith);
  129. return result;
  130. }
  131. };
  132. //==============================================================================
  133. static bool addFile (const std::string& filePath,
  134. const std::string& binaryNamespace,
  135. std::ofstream& headerStream,
  136. std::ofstream& cppStream,
  137. bool verbose)
  138. {
  139. std::ifstream fileStream (filePath, std::ios::in | std::ios::binary | std::ios::ate);
  140. if (! fileStream.is_open())
  141. {
  142. std::cerr << "Failed to open input file " << filePath << std::endl;
  143. return false;
  144. }
  145. std::vector<char> buffer ((size_t) fileStream.tellg());
  146. fileStream.seekg (0);
  147. fileStream.read (buffer.data(), static_cast<std::streamsize> (buffer.size()));
  148. const auto variableName = StringHelpers::replaced (StringHelpers::replaced (FileHelpers::getFilename (filePath),
  149. ' ',
  150. '_'),
  151. '.',
  152. '_');
  153. if (verbose)
  154. {
  155. std::cout << "Adding " << variableName << ": "
  156. << buffer.size() << " bytes" << std::endl;
  157. }
  158. headerStream << " extern const char* " << variableName << ";" << std::endl
  159. << " const int " << variableName << "Size = "
  160. << buffer.size() << ";" << std::endl;
  161. cppStream << "static const unsigned char temp0[] = {";
  162. auto* data = (const uint8_t*) buffer.data();
  163. for (size_t i = 0; i < buffer.size() - 1; ++i)
  164. {
  165. cppStream << (int) data[i] << ",";
  166. if ((i % 40) == 39)
  167. cppStream << std::endl << " ";
  168. }
  169. cppStream << (int) data[buffer.size() - 1] << ",0,0};" << std::endl;
  170. cppStream << "const char* " << binaryNamespace << "::" << variableName
  171. << " = (const char*) temp0" << ";" << std::endl << std::endl;
  172. return true;
  173. }
  174. //==============================================================================
  175. class Arguments
  176. {
  177. public:
  178. enum class PositionalArguments
  179. {
  180. sourceFile = 0,
  181. targetDirectory,
  182. targetFilename,
  183. binaryNamespace
  184. };
  185. static std::optional<Arguments> create (int argc, char* argv[])
  186. {
  187. std::vector<std::string> arguments;
  188. bool verbose = false;
  189. for (int i = 1; i < argc; ++i)
  190. {
  191. std::string arg { argv[i] };
  192. if (arg == "-v" || arg == "--verbose")
  193. verbose = true;
  194. else
  195. arguments.emplace_back (std::move (arg));
  196. }
  197. if (arguments.size() != static_cast<size_t> (PositionalArguments::binaryNamespace) + 1)
  198. return std::nullopt;
  199. return Arguments { std::move (arguments), verbose };
  200. }
  201. std::string get (PositionalArguments argument) const
  202. {
  203. return arguments[static_cast<size_t> (argument)];
  204. }
  205. bool isVerbose() const
  206. {
  207. return verbose;
  208. }
  209. private:
  210. Arguments (std::vector<std::string> args, bool verboseIn)
  211. : arguments (std::move (args)), verbose (verboseIn)
  212. {
  213. }
  214. std::vector<std::string> arguments;
  215. bool verbose = false;
  216. };
  217. //==============================================================================
  218. int main (int argc, char* argv[])
  219. {
  220. const auto arguments = Arguments::create (argc, argv);
  221. if (! arguments.has_value())
  222. {
  223. std::cout << " Usage: SimpleBinaryBuilder [-v | --verbose] sourcefile targetdirectory targetfilename namespace"
  224. << std::endl << std::endl
  225. << " SimpleBinaryBuilder will encode the provided source file into" << std::endl
  226. << " two files called (targetfilename).cpp and (targetfilename).h," << std::endl
  227. << " which it will write into the specified target directory." << std::endl
  228. << " The target directory will be automatically created if necessary. The binary" << std::endl
  229. << " resource will be available in the given namespace." << std::endl << std::endl;
  230. return 0;
  231. }
  232. const auto currentWorkingDirectory = FileHelpers::getCurrentWorkingDirectory();
  233. using ArgType = Arguments::PositionalArguments;
  234. const auto sourceFile = FileHelpers::appendedPaths (currentWorkingDirectory,
  235. StringHelpers::unquoted (arguments->get (ArgType::sourceFile)));
  236. if (! FileHelpers::exists (sourceFile))
  237. {
  238. std::cerr << "Source file doesn't exist: "
  239. << sourceFile
  240. << std::endl << std::endl;
  241. return 1;
  242. }
  243. const auto targetDirectory = FileHelpers::appendedPaths (currentWorkingDirectory,
  244. StringHelpers::unquoted (arguments->get (ArgType::targetDirectory)));
  245. if (! FileHelpers::exists (targetDirectory))
  246. {
  247. if (! FileHelpers::createDirectory (targetDirectory))
  248. {
  249. std::cerr << "Failed to create target directory: " << targetDirectory << std::endl;
  250. return 1;
  251. }
  252. }
  253. const auto className = StringHelpers::trimmed (arguments->get (ArgType::targetFilename));
  254. const auto binaryNamespace = StringHelpers::trimmed (arguments->get (ArgType::binaryNamespace));
  255. const auto headerFilePath = FileHelpers::appendedPaths (targetDirectory, className + ".h");
  256. const auto cppFilePath = FileHelpers::appendedPaths (targetDirectory, className + ".cpp");
  257. if (arguments->isVerbose())
  258. {
  259. std::cout << "Creating " << headerFilePath
  260. << " and " << cppFilePath
  261. << " from file " << sourceFile
  262. << "..." << std::endl << std::endl;
  263. }
  264. if (! FileHelpers::deleteFile (headerFilePath))
  265. {
  266. std::cerr << "Failed to remove old header file: " << headerFilePath << std::endl;
  267. return 1;
  268. }
  269. if (! FileHelpers::deleteFile (cppFilePath))
  270. {
  271. std::cerr << "Failed to remove old source file: " << cppFilePath << std::endl;
  272. return 1;
  273. }
  274. std::ofstream header (headerFilePath);
  275. if (! header.is_open())
  276. {
  277. std::cerr << "Failed to open " << headerFilePath << std::endl;
  278. return 1;
  279. }
  280. std::ofstream cpp (cppFilePath);
  281. if (! cpp.is_open())
  282. {
  283. std::cerr << "Failed to open " << headerFilePath << std::endl;
  284. return 1;
  285. }
  286. header << "/* (Auto-generated binary data file). */" << std::endl << std::endl
  287. << "#pragma once" << std::endl << std::endl
  288. << "namespace " << binaryNamespace << std::endl
  289. << "{" << std::endl;
  290. cpp << "/* (Auto-generated binary data file). */" << std::endl << std::endl
  291. << "#include " << std::quoted (className + ".h") << std::endl << std::endl;
  292. if (! addFile (sourceFile, binaryNamespace, header, cpp, arguments->isVerbose()))
  293. return 1;
  294. header << "}" << std::endl << std::endl;
  295. return 0;
  296. }