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.

376 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. struct stat64 info;
  65. return ! path.empty()
  66. && stat64 (path.c_str(), &info) == 0
  67. && ((info.st_mode & S_IFDIR) != 0);
  68. }
  69. static std::string getParentDirectory (const std::string& path)
  70. {
  71. std::string p { path.begin(),
  72. std::find_if (path.rbegin(),
  73. path.rend(),
  74. [] (auto c) { return c == '/'; }).base() };
  75. // Trim the ending slash, but only if not root
  76. if (endsWith (p, '/') && p.length() > 1)
  77. return { p.begin(), p.end() - 1 };
  78. return p;
  79. }
  80. static bool createDirectory (const std::string& path)
  81. {
  82. if (isDirectory (path))
  83. return true;
  84. const auto parentDir = getParentDirectory (path);
  85. if (path == parentDir)
  86. return false;
  87. if (createDirectory (parentDir))
  88. return mkdir (path.c_str(), 0777) != -1;
  89. return false;
  90. }
  91. };
  92. //==============================================================================
  93. struct StringHelpers
  94. {
  95. static bool isQuoteCharacter (char c)
  96. {
  97. return c == '"' || c == '\'';
  98. }
  99. static std::string unquoted (const std::string& str)
  100. {
  101. if (str.length() == 0 || (! isQuoteCharacter (str[0])))
  102. return str;
  103. return str.substr (1, str.length() - (isQuoteCharacter (str[str.length() - 1]) ? 1 : 0));
  104. }
  105. static void ltrim (std::string& s)
  106. {
  107. s.erase (s.begin(), std::find_if (s.begin(), s.end(), [] (int c) { return ! std::isspace (c); }));
  108. }
  109. static void rtrim (std::string& s)
  110. {
  111. s.erase (std::find_if (s.rbegin(), s.rend(), [] (int c) { return ! std::isspace (c); }).base(), s.end());
  112. }
  113. static std::string trimmed (const std::string& str)
  114. {
  115. auto result = str;
  116. ltrim (result);
  117. rtrim (result);
  118. return result;
  119. }
  120. static std::string replaced (const std::string& str, char charToReplace, char replaceWith)
  121. {
  122. auto result = str;
  123. std::replace (result.begin(), result.end(), charToReplace, replaceWith);
  124. return result;
  125. }
  126. };
  127. //==============================================================================
  128. static bool addFile (const std::string& filePath,
  129. const std::string& binaryNamespace,
  130. std::ofstream& headerStream,
  131. std::ofstream& cppStream,
  132. bool verbose)
  133. {
  134. std::ifstream fileStream (filePath, std::ios::in | std::ios::binary | std::ios::ate);
  135. if (! fileStream.is_open())
  136. {
  137. std::cerr << "Failed to open input file " << filePath << std::endl;
  138. return false;
  139. }
  140. std::vector<char> buffer ((size_t) fileStream.tellg());
  141. fileStream.seekg (0);
  142. fileStream.read (buffer.data(), static_cast<std::streamsize> (buffer.size()));
  143. const auto variableName = StringHelpers::replaced (StringHelpers::replaced (FileHelpers::getFilename (filePath),
  144. ' ',
  145. '_'),
  146. '.',
  147. '_');
  148. if (verbose)
  149. {
  150. std::cout << "Adding " << variableName << ": "
  151. << buffer.size() << " bytes" << std::endl;
  152. }
  153. headerStream << " extern const char* " << variableName << ";" << std::endl
  154. << " const int " << variableName << "Size = "
  155. << buffer.size() << ";" << std::endl;
  156. cppStream << "static const unsigned char temp0[] = {";
  157. auto* data = (const uint8_t*) buffer.data();
  158. for (size_t i = 0; i < buffer.size() - 1; ++i)
  159. {
  160. cppStream << (int) data[i] << ",";
  161. if ((i % 40) == 39)
  162. cppStream << std::endl << " ";
  163. }
  164. cppStream << (int) data[buffer.size() - 1] << ",0,0};" << std::endl;
  165. cppStream << "const char* " << binaryNamespace << "::" << variableName
  166. << " = (const char*) temp0" << ";" << std::endl << std::endl;
  167. return true;
  168. }
  169. //==============================================================================
  170. class Arguments
  171. {
  172. public:
  173. enum class PositionalArguments
  174. {
  175. sourceFile = 0,
  176. targetDirectory,
  177. targetFilename,
  178. binaryNamespace
  179. };
  180. static std::optional<Arguments> create (int argc, char* argv[])
  181. {
  182. std::vector<std::string> arguments;
  183. bool verbose = false;
  184. for (int i = 1; i < argc; ++i)
  185. {
  186. std::string arg { argv[i] };
  187. if (arg == "-v" || arg == "--verbose")
  188. verbose = true;
  189. else
  190. arguments.emplace_back (std::move (arg));
  191. }
  192. if (arguments.size() != static_cast<size_t> (PositionalArguments::binaryNamespace) + 1)
  193. return std::nullopt;
  194. return Arguments { std::move (arguments), verbose };
  195. }
  196. std::string get (PositionalArguments argument) const
  197. {
  198. return arguments[static_cast<size_t> (argument)];
  199. }
  200. bool isVerbose() const
  201. {
  202. return verbose;
  203. }
  204. private:
  205. Arguments (std::vector<std::string> args, bool verboseIn)
  206. : arguments (std::move (args)), verbose (verboseIn)
  207. {
  208. }
  209. std::vector<std::string> arguments;
  210. bool verbose = false;
  211. };
  212. //==============================================================================
  213. int main (int argc, char* argv[])
  214. {
  215. const auto arguments = Arguments::create (argc, argv);
  216. if (! arguments.has_value())
  217. {
  218. std::cout << " Usage: SimpleBinaryBuilder [-v | --verbose] sourcefile targetdirectory targetfilename namespace"
  219. << std::endl << std::endl
  220. << " SimpleBinaryBuilder will encode the provided source file into" << std::endl
  221. << " two files called (targetfilename).cpp and (targetfilename).h," << std::endl
  222. << " which it will write into the specified target directory." << std::endl
  223. << " The target directory will be automatically created if necessary. The binary" << std::endl
  224. << " resource will be available in the given namespace." << std::endl << std::endl;
  225. return 0;
  226. }
  227. const auto currentWorkingDirectory = FileHelpers::getCurrentWorkingDirectory();
  228. using ArgType = Arguments::PositionalArguments;
  229. const auto sourceFile = FileHelpers::appendedPaths (currentWorkingDirectory,
  230. StringHelpers::unquoted (arguments->get (ArgType::sourceFile)));
  231. if (! FileHelpers::exists (sourceFile))
  232. {
  233. std::cerr << "Source file doesn't exist: "
  234. << sourceFile
  235. << std::endl << std::endl;
  236. return 1;
  237. }
  238. const auto targetDirectory = FileHelpers::appendedPaths (currentWorkingDirectory,
  239. StringHelpers::unquoted (arguments->get (ArgType::targetDirectory)));
  240. if (! FileHelpers::exists (targetDirectory))
  241. {
  242. if (! FileHelpers::createDirectory (targetDirectory))
  243. {
  244. std::cerr << "Failed to create target directory: " << targetDirectory << std::endl;
  245. return 1;
  246. }
  247. }
  248. const auto className = StringHelpers::trimmed (arguments->get (ArgType::targetFilename));
  249. const auto binaryNamespace = StringHelpers::trimmed (arguments->get (ArgType::binaryNamespace));
  250. const auto headerFilePath = FileHelpers::appendedPaths (targetDirectory, className + ".h");
  251. const auto cppFilePath = FileHelpers::appendedPaths (targetDirectory, className + ".cpp");
  252. if (arguments->isVerbose())
  253. {
  254. std::cout << "Creating " << headerFilePath
  255. << " and " << cppFilePath
  256. << " from file " << sourceFile
  257. << "..." << std::endl << std::endl;
  258. }
  259. if (! FileHelpers::deleteFile (headerFilePath))
  260. {
  261. std::cerr << "Failed to remove old header file: " << headerFilePath << std::endl;
  262. return 1;
  263. }
  264. if (! FileHelpers::deleteFile (cppFilePath))
  265. {
  266. std::cerr << "Failed to remove old source file: " << cppFilePath << std::endl;
  267. return 1;
  268. }
  269. std::ofstream header (headerFilePath);
  270. if (! header.is_open())
  271. {
  272. std::cerr << "Failed to open " << headerFilePath << std::endl;
  273. return 1;
  274. }
  275. std::ofstream cpp (cppFilePath);
  276. if (! cpp.is_open())
  277. {
  278. std::cerr << "Failed to open " << headerFilePath << std::endl;
  279. return 1;
  280. }
  281. header << "/* (Auto-generated binary data file). */" << std::endl << std::endl
  282. << "#pragma once" << std::endl << std::endl
  283. << "namespace " << binaryNamespace << std::endl
  284. << "{" << std::endl;
  285. cpp << "/* (Auto-generated binary data file). */" << std::endl << std::endl
  286. << "#include " << std::quoted (className + ".h") << std::endl << std::endl;
  287. if (! addFile (sourceFile, binaryNamespace, header, cpp, arguments->isVerbose()))
  288. return 1;
  289. header << "}" << std::endl << std::endl;
  290. return 0;
  291. }