1
0

build-web-examples.pl 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420
  1. #!/usr/bin/perl -w
  2. # Simple DirectMedia Layer
  3. # Copyright (C) 1997-2025 Sam Lantinga <slouken@libsdl.org>
  4. #
  5. # This software is provided 'as-is', without any express or implied
  6. # warranty. In no event will the authors be held liable for any damages
  7. # arising from the use of this software.
  8. #
  9. # Permission is granted to anyone to use this software for any purpose,
  10. # including commercial applications, and to alter it and redistribute it
  11. # freely, subject to the following restrictions:
  12. #
  13. # 1. The origin of this software must not be misrepresented; you must not
  14. # claim that you wrote the original software. If you use this software
  15. # in a product, an acknowledgment in the product documentation would be
  16. # appreciated but is not required.
  17. # 2. Altered source versions must be plainly marked as such, and must not be
  18. # misrepresented as being the original software.
  19. # 3. This notice may not be removed or altered from any source distribution.
  20. use warnings;
  21. use strict;
  22. use File::Basename;
  23. use File::Copy;
  24. use Cwd qw(abs_path);
  25. use IPC::Open2;
  26. my $examples_dir = abs_path(dirname(__FILE__) . "/../examples");
  27. my $project = undef;
  28. my $emsdk_dir = undef;
  29. my $compile_dir = undef;
  30. my $cmake_flags = undef;
  31. my $output_dir = undef;
  32. sub usage {
  33. die("USAGE: $0 <project_name> <emsdk_dir> <compiler_output_directory> <cmake_flags> <html_output_directory>\n\n");
  34. }
  35. sub do_system {
  36. my $cmd = shift;
  37. $cmd = "exec /bin/bash -c \"$cmd\"";
  38. print("$cmd\n");
  39. return system($cmd);
  40. }
  41. sub do_mkdir {
  42. my $d = shift;
  43. if ( ! -d $d ) {
  44. print("mkdir '$d'\n");
  45. mkdir($d) or die("Couldn't mkdir('$d'): $!\n");
  46. }
  47. }
  48. sub do_copy {
  49. my $src = shift;
  50. my $dst = shift;
  51. print("cp '$src' '$dst'\n");
  52. copy($src, $dst) or die("Failed to copy '$src' to '$dst': $!\n");
  53. }
  54. sub build_latest {
  55. # Try to build just the latest without re-running cmake, since that is SLOW.
  56. print("Building latest version of $project ...\n");
  57. if (do_system("EMSDK_QUIET=1 source '$emsdk_dir/emsdk_env.sh' && cd '$compile_dir' && ninja") != 0) {
  58. # Build failed? Try nuking the build dir and running CMake from scratch.
  59. print("\n\nBuilding latest version of $project FROM SCRATCH ...\n");
  60. if (do_system("EMSDK_QUIET=1 source '$emsdk_dir/emsdk_env.sh' && rm -rf '$compile_dir' && mkdir '$compile_dir' && cd '$compile_dir' && emcmake cmake -G Ninja -DCMAKE_BUILD_TYPE=MinSizeRel $cmake_flags '$examples_dir/..' && ninja") != 0) {
  61. die("Failed to build latest version of $project!\n"); # oh well.
  62. }
  63. }
  64. }
  65. sub get_category_description {
  66. my $category = shift;
  67. my $retval = ucfirst($category);
  68. if (open(my $fh, '<', "$examples_dir/$category/description.txt")) {
  69. $retval = <$fh>;
  70. chomp($retval);
  71. close($fh);
  72. }
  73. return $retval;
  74. }
  75. sub get_categories {
  76. my @categories = ();
  77. if (open(my $fh, '<', "$examples_dir/categories.txt")) {
  78. while (<$fh>) {
  79. chomp;
  80. s/\A\s+//;
  81. s/\s+\Z//;
  82. next if $_ eq '';
  83. next if /\A\#/;
  84. push @categories, $_;
  85. }
  86. close($fh);
  87. } else {
  88. opendir(my $dh, $examples_dir) or die("Couldn't opendir '$examples_dir': $!\n");
  89. foreach my $dir (sort readdir $dh) {
  90. next if ($dir eq '.') || ($dir eq '..'); # obviously skip current and parent entries.
  91. next if not -d "$examples_dir/$dir"; # only care about subdirectories.
  92. push @categories, $dir;
  93. }
  94. closedir($dh);
  95. }
  96. return @categories;
  97. }
  98. sub get_examples_for_category {
  99. my $category = shift;
  100. my @examples = ();
  101. opendir(my $dh, "$examples_dir/$category") or die("Couldn't opendir '$examples_dir/$category': $!\n");
  102. foreach my $dir (sort readdir $dh) {
  103. next if ($dir eq '.') || ($dir eq '..'); # obviously skip current and parent entries.
  104. next if not -d "$examples_dir/$category/$dir"; # only care about subdirectories.
  105. push @examples, $dir;
  106. }
  107. closedir($dh);
  108. return @examples;
  109. }
  110. sub handle_example_dir {
  111. my $category = shift;
  112. my $example = shift;
  113. my @files = ();
  114. my $files_str = '';
  115. opendir(my $dh, "$examples_dir/$category/$example") or die("Couldn't opendir '$examples_dir/$category/$example': $!\n");
  116. my $spc = '';
  117. while (readdir($dh)) {
  118. my $path = "$examples_dir/$category/$example/$_";
  119. next if not -f $path; # only care about files.
  120. push @files, $path if /\.[ch]\Z/; # add .c and .h files to source code.
  121. if (/\.c\Z/) { # only care about .c files for compiling.
  122. $files_str .= "$spc$path";
  123. $spc = ' ';
  124. }
  125. }
  126. closedir($dh);
  127. my $dst = "$output_dir/$category/$example";
  128. print("Building $category/$example ...\n");
  129. my $basefname = "$example";
  130. $basefname =~ s/\A\d+\-//;
  131. $basefname = "$category-$basefname";
  132. my $jsfname = "$basefname.js";
  133. my $wasmfname = "$basefname.wasm";
  134. my $thumbnailfname = 'thumbnail.png';
  135. my $onmouseoverfname = 'onmouseover.webp';
  136. my $jssrc = "$compile_dir/examples/$jsfname";
  137. my $wasmsrc = "$compile_dir/examples/$wasmfname";
  138. my $thumbnailsrc = "$examples_dir/$category/$example/$thumbnailfname";
  139. my $onmouseoversrc = "$examples_dir/$category/$example/$onmouseoverfname";
  140. my $jsdst = "$dst/$jsfname";
  141. my $wasmdst = "$dst/$wasmfname";
  142. my $thumbnaildst = "$dst/$thumbnailfname";
  143. my $onmouseoverdst = "$dst/$onmouseoverfname";
  144. my $description = '';
  145. my $has_paragraph = 0;
  146. if (open(my $readmetxth, '<', "$examples_dir/$category/$example/README.txt")) {
  147. while (<$readmetxth>) {
  148. chomp;
  149. s/\A\s+//;
  150. s/\s+\Z//;
  151. if (($_ eq '') && ($description ne '')) {
  152. $has_paragraph = 1;
  153. } else {
  154. if ($has_paragraph) {
  155. $description .= "\n<br/>\n<br/>\n";
  156. $has_paragraph = 0;
  157. }
  158. $description .= "$_ ";
  159. }
  160. }
  161. close($readmetxth);
  162. $description =~ s/\s+\Z//;
  163. }
  164. do_mkdir($dst);
  165. do_copy($jssrc, $jsdst);
  166. do_copy($wasmsrc, $wasmdst);
  167. do_copy($thumbnailsrc, $thumbnaildst) if ( -f $thumbnailsrc );
  168. do_copy($onmouseoversrc, $onmouseoverdst) if ( -f $onmouseoversrc );
  169. my $highlight_cmd = "highlight '--outdir=$dst' --style-outfile=highlight.css --fragment --enclose-pre --stdout --syntax=c '--plug-in=$examples_dir/highlight-plugin.lua'";
  170. print("$highlight_cmd\n");
  171. my $pid = open2(my $child_out, my $child_in, $highlight_cmd);
  172. my $htmlified_source_code = '';
  173. foreach (sort(@files)) {
  174. my $path = $_;
  175. open my $srccode, '<', $path or die("Couldn't open '$path': $!\n");
  176. my $fname = "$path";
  177. $fname =~ s/\A.*\///;
  178. print $child_in "/* $fname ... */\n\n";
  179. while (<$srccode>) {
  180. print $child_in $_;
  181. }
  182. print $child_in "\n\n\n";
  183. close($srccode);
  184. }
  185. close($child_in);
  186. while (<$child_out>) {
  187. $htmlified_source_code .= $_;
  188. }
  189. close($child_out);
  190. waitpid($pid, 0);
  191. my $other_examples_html = "<ul>";
  192. foreach my $example (get_examples_for_category($category)) {
  193. $other_examples_html .= "<li><a href='/$project/$category/$example'>$category/$example</a></li>";
  194. }
  195. $other_examples_html .= "</ul>";
  196. my $category_description = get_category_description($category);
  197. my $preview_image = get_example_thumbnail($project, $category, $example);
  198. my $html = '';
  199. open my $htmltemplate, '<', "$examples_dir/template.html" or die("Couldn't open '$examples_dir/template.html': $!\n");
  200. while (<$htmltemplate>) {
  201. s/\@project_name\@/$project/g;
  202. s/\@category_name\@/$category/g;
  203. s/\@category_description\@/$category_description/g;
  204. s/\@example_name\@/$example/g;
  205. s/\@javascript_file\@/$jsfname/g;
  206. s/\@htmlified_source_code\@/$htmlified_source_code/g;
  207. s/\@description\@/$description/g;
  208. s/\@preview_image\@/$preview_image/g;
  209. s/\@other_examples_html\@/$other_examples_html/g;
  210. $html .= $_;
  211. }
  212. close($htmltemplate);
  213. open my $htmloutput, '>', "$dst/index.html" or die("Couldn't open '$dst/index.html': $!\n");
  214. print $htmloutput $html;
  215. close($htmloutput);
  216. }
  217. sub get_example_thumbnail {
  218. my $project = shift;
  219. my $category = shift;
  220. my $example = shift;
  221. if ( -f "$examples_dir/$category/$example/thumbnail.png" ) {
  222. return "/$project/$category/$example/thumbnail.png";
  223. } elsif ( -f "$examples_dir/$category/thumbnail.png" ) {
  224. return "/$project/$category/thumbnail.png";
  225. }
  226. return "/$project/thumbnail.png";
  227. }
  228. sub generate_example_thumbnail {
  229. my $project = shift;
  230. my $category = shift;
  231. my $example = shift;
  232. my $example_no_num = "$example";
  233. $example_no_num =~ s/\A\d+\-//;
  234. my $example_image_url = get_example_thumbnail($project, $category, $example);
  235. my $example_mouseover_html = '';
  236. if ( -f "$examples_dir/$category/$example/onmouseover.webp" ) {
  237. $example_mouseover_html = "onmouseover=\"this.src='/$project/$category/$example/onmouseover.webp'\" onmouseout=\"this.src='$example_image_url';\"";
  238. } elsif ( -f "$examples_dir/$category/onmouseover.webp" ) {
  239. $example_mouseover_html = "onmouseover=\"this.src='/$project/$category/onmouseover.webp'\" onmouseout=\"this.src='$example_image_url';\"";
  240. }
  241. return "
  242. <a href='/$project/$category/$example'>
  243. <div>
  244. <img src='$example_image_url' $example_mouseover_html />
  245. <div>$example_no_num</div>
  246. </div>
  247. </a>"
  248. ;
  249. }
  250. sub generate_example_thumbnails_for_category {
  251. my $project = shift;
  252. my $category = shift;
  253. my @examples = get_examples_for_category($category);
  254. my $retval = '';
  255. foreach my $example (@examples) {
  256. $retval .= generate_example_thumbnail($project, $category, $example);
  257. }
  258. return $retval;
  259. }
  260. sub handle_category_dir {
  261. my $category = shift;
  262. print("Category $category ...\n");
  263. do_mkdir("$output_dir/$category");
  264. opendir(my $dh, "$examples_dir/$category") or die("Couldn't opendir '$examples_dir/$category': $!\n");
  265. while (readdir($dh)) {
  266. next if ($_ eq '.') || ($_ eq '..'); # obviously skip current and parent entries.
  267. next if not -d "$examples_dir/$category/$_"; # only care about subdirectories.
  268. handle_example_dir($category, $_);
  269. }
  270. closedir($dh);
  271. my $examples_list_html = generate_example_thumbnails_for_category($project, $category);
  272. my $dst = "$output_dir/$category";
  273. do_copy("$examples_dir/$category/thumbnail.png", "$dst/thumbnail.png") if ( -f "$examples_dir/$category/thumbnail.png" );
  274. do_copy("$examples_dir/$category/onmouseover.webp", "$dst/onmouseover.webp") if ( -f "$examples_dir/$category/onmouseover.webp" );
  275. my $category_description = get_category_description($category);
  276. my $preview_image = "/$project/thumbnail.png";
  277. if ( -f "$examples_dir/$category/thumbnail.png" ) {
  278. $preview_image = "/$project/$category/thumbnail.png";
  279. }
  280. # write category page
  281. my $html = '';
  282. open my $htmltemplate, '<', "$examples_dir/template-category.html" or die("Couldn't open '$examples_dir/template-category.html': $!\n");
  283. while (<$htmltemplate>) {
  284. s/\@project_name\@/$project/g;
  285. s/\@category_name\@/$category/g;
  286. s/\@category_description\@/$category_description/g;
  287. s/\@examples_list_html\@/$examples_list_html/g;
  288. s/\@preview_image\@/$preview_image/g;
  289. $html .= $_;
  290. }
  291. close($htmltemplate);
  292. open my $htmloutput, '>', "$dst/index.html" or die("Couldn't open '$dst/index.html': $!\n");
  293. print $htmloutput $html;
  294. close($htmloutput);
  295. }
  296. # Mainline!
  297. foreach (@ARGV) {
  298. $project = $_, next if not defined $project;
  299. $emsdk_dir = $_, next if not defined $emsdk_dir;
  300. $compile_dir = $_, next if not defined $compile_dir;
  301. $cmake_flags = $_, next if not defined $cmake_flags;
  302. $output_dir = $_, next if not defined $output_dir;
  303. usage(); # too many arguments.
  304. }
  305. usage() if not defined $output_dir;
  306. print("Examples dir: $examples_dir\n");
  307. print("emsdk dir: $emsdk_dir\n");
  308. print("Compile dir: $compile_dir\n");
  309. print("CMake flags: $cmake_flags\n");
  310. print("Output dir: $output_dir\n");
  311. do_system("rm -rf '$output_dir'");
  312. do_mkdir($output_dir);
  313. build_latest();
  314. do_copy("$examples_dir/template.css", "$output_dir/examples.css");
  315. do_copy("$examples_dir/template-placeholder.png", "$output_dir/thumbnail.png");
  316. opendir(my $dh, $examples_dir) or die("Couldn't opendir '$examples_dir': $!\n");
  317. while (readdir($dh)) {
  318. next if ($_ eq '.') || ($_ eq '..'); # obviously skip current and parent entries.
  319. next if not -d "$examples_dir/$_"; # only care about subdirectories.
  320. # !!! FIXME: this needs to generate a preview page for all the categories.
  321. handle_category_dir($_);
  322. }
  323. closedir($dh);
  324. # write homepage
  325. my $homepage_list_html = "";
  326. foreach my $category (get_categories()) {
  327. my $category_description = get_category_description($category);
  328. $homepage_list_html .= "<h2>$category_description</h2>";
  329. $homepage_list_html .= "<div class='list'>";
  330. $homepage_list_html .= generate_example_thumbnails_for_category($project, $category);
  331. $homepage_list_html .= "</div>";
  332. }
  333. my $preview_image = "/$project/thumbnail.png";
  334. my $dst = "$output_dir/";
  335. my $html = '';
  336. open my $htmltemplate, '<', "$examples_dir/template-homepage.html" or die("Couldn't open '$examples_dir/template-category.html': $!\n");
  337. while (<$htmltemplate>) {
  338. s/\@project_name\@/$project/g;
  339. s/\@homepage_list_html\@/$homepage_list_html/g;
  340. s/\@preview_image\@/$preview_image/g;
  341. $html .= $_;
  342. }
  343. close($htmltemplate);
  344. open my $htmloutput, '>', "$dst/index.html" or die("Couldn't open '$dst/index.html': $!\n");
  345. print $htmloutput $html;
  346. close($htmloutput);
  347. print("All examples built successfully!\n");
  348. exit(0); # success!