From 83fb97b2d40cfe7d09a7617578a0d6b14d367c20 Mon Sep 17 00:00:00 2001 From: GarboMuffin <muffin@mailbox.org> Date: Wed, 3 Jan 2024 20:58:43 -0600 Subject: [PATCH] Fix building on Windows or with Python 3.12 (#7) --- .gitignore | 2 ++ README.md | 12 ++++++-- build.py | 83 +++++++++++++++++++++++++++++++----------------------- 3 files changed, 58 insertions(+), 39 deletions(-) diff --git a/.gitignore b/.gitignore index ca5e13595f..58963ca197 100644 --- a/.gitignore +++ b/.gitignore @@ -44,3 +44,5 @@ python_compressed.js /*compiler*.jar /local_blockly_compressed_vertical.js /chromedriver +# --flagfiles used on Windows +/*.config diff --git a/README.md b/README.md index 145c78e49b..459fc02018 100644 --- a/README.md +++ b/README.md @@ -6,21 +6,27 @@ ## Local development +Requires Node.js (`node`), Python (`python3`), and Java (`java`). It is known to work in these environments but should work in many others: + + - Windows 10, Python 3.12.1 (Microsoft Store), Node.js 20.10.0 (nodejs.org installer), Java 11 (Temurin-11.0.21+9) + - macOS 14.2.1, Python 3.11.6 (Apple), Node.js 20.10.0 (installed manually), Java 21 (Temurin-21.0.1+12) + - Ubuntu 22.04, Python 3.10.12 (python3 package), Node.js 20.10.0 (installed manually), Java 11 (openjdk-11-jre package) + Install dependencies: ```sh npm ci ``` -The playground to use for local testing is tests/vertical_playground.html. +Open tests/vertical_playground.html in a browser for development. You don't need to rebuild compressed versions for most changes. Open tests/vertical_playground_compressed.html instead to test if the compressed versions built properly. -To build, run: +To re-build compressed versions, run: ```sh npm run prepublish ``` -requires Python (2 or 3). scratch-gui development server must be restarted to update linked scratch-blocks. +scratch-gui development server must be restarted to update linked scratch-blocks. <!-- #### Scratch Blocks is a library for building creative computing interfaces. diff --git a/build.py b/build.py index 3fc56e5fd3..44fc4dacfc 100755 --- a/build.py +++ b/build.py @@ -36,7 +36,7 @@ import sys -import errno, glob, json, os, re, subprocess, threading, codecs, functools +import errno, glob, json, os, re, subprocess, threading, codecs, functools, platform if sys.version_info[0] == 2: import httplib @@ -116,7 +116,7 @@ def run(self): if (!isNodeJS) { // Find name of current directory. var scripts = document.getElementsByTagName('script'); - var re = new RegExp('(.+)[\/]blockly_uncompressed(_vertical|_horizontal|)\.js$'); + var re = new RegExp('(.+)[\\/]blockly_uncompressed(_vertical|_horizontal|)\\.js$'); for (var i = 0, script; script = scripts[i]; i++) { var match = re.exec(script.src); if (match) { @@ -333,9 +333,21 @@ def do_compile_local(self, params, target_filename): for group in [[CLOSURE_COMPILER_NPM], dash_args]: args.extend(filter(lambda item: item, group)) + # On Windows, the command line is too long, so we save the arguments to a file instead + use_flagfile = platform.system() == "Windows" + if platform.system() == "Windows": + flagfile_name = target_filename + ".config" + with open(flagfile_name, "w") as f: + # \ needs to be escaped still + f.write(" ".join(args[1:]).replace("\\", "\\\\")) + args = [CLOSURE_COMPILER_NPM, "--flagfile", flagfile_name] + proc = subprocess.Popen(args, stdin=subprocess.PIPE, stdout=subprocess.PIPE) (stdout, stderr) = proc.communicate() + if use_flagfile: + os.remove(flagfile_name) + # Build the JSON response. filesizes = [os.path.getsize(value) for (arg, value) in params if arg == "js_file"] return dict( @@ -439,12 +451,12 @@ def write_output(self, target_filename, remove, json_data): # The Closure Compiler preserves these. LICENSE = re.compile("""/\\* - [\w ]+ + [\\w ]+ Copyright \\d+ Google Inc. https://developers.google.com/blockly/ - Licensed under the Apache License, Version 2.0 \(the "License"\); + Licensed under the Apache License, Version 2.0 \\(the "License"\\); you may not use this file except in compliance with the License. You may obtain a copy of the License at @@ -583,28 +595,17 @@ def exclude_horizontal(item): print("Using local compiler: %s ...\n" % CLOSURE_COMPILER_NPM) except (ImportError, AssertionError): - print("Using remote compiler: closure-compiler.appspot.com ...\n") - - try: - closure_dir = CLOSURE_DIR - closure_root = CLOSURE_ROOT - closure_library = CLOSURE_LIBRARY - closure_compiler = CLOSURE_COMPILER - - calcdeps = import_path(os.path.join( - closure_root, closure_library, "closure", "bin", "calcdeps.py")) - except ImportError: - if os.path.isdir(os.path.join(os.path.pardir, "closure-library-read-only")): - # Dir got renamed when Closure moved from Google Code to GitHub in 2014. - print("Error: Closure directory needs to be renamed from" - "'closure-library-read-only' to 'closure-library'.\n" - "Please rename this directory.") - elif os.path.isdir(os.path.join(os.path.pardir, "google-closure-library")): - print("Error: Closure directory needs to be renamed from" - "'google-closure-library' to 'closure-library'.\n" - "Please rename this directory.") - else: - print("""Error: Closure not found. Read this: + if os.path.isdir(os.path.join(os.path.pardir, "closure-library-read-only")): + # Dir got renamed when Closure moved from Google Code to GitHub in 2014. + print("Error: Closure directory needs to be renamed from" + "'closure-library-read-only' to 'closure-library'.\n" + "Please rename this directory.") + elif os.path.isdir(os.path.join(os.path.pardir, "google-closure-library")): + print("Error: Closure directory needs to be renamed from" + "'google-closure-library' to 'closure-library'.\n" + "Please rename this directory.") + else: + print("""Error: Closure not found. Usually this means 'npm ci' failed. Try running it again? More resources: developers.google.com/blockly/guides/modify/web/closure""") sys.exit(1) @@ -624,13 +625,23 @@ def exclude_horizontal(item): # Run all tasks in parallel threads. # Uncompressed is limited by processor speed. # Compressed is limited by network and server speed. - # Vertical: - Gen_uncompressed(search_paths_vertical, True, closure_env).start() - # Horizontal: - Gen_uncompressed(search_paths_horizontal, False, closure_env).start() - - # Compressed forms of vertical and horizontal. - Gen_compressed(search_paths_vertical, search_paths_horizontal, closure_env).start() - - # This is run locally in a separate thread. - # Gen_langfiles().start() + threads = [ + # Vertical: + Gen_uncompressed(search_paths_vertical, True, closure_env), + # Horizontal: + Gen_uncompressed(search_paths_horizontal, False, closure_env), + # Compressed forms of vertical and horizontal. + Gen_compressed(search_paths_vertical, search_paths_horizontal, closure_env), + + # This is run locally in a separate thread. + # Gen_langfiles() + ] + + for thread in threads: + thread.start() + + # Need to wait for all threads to finish before the main process ends as in Python 3.12, + # once the main interpreter is being shutdown, trying to spawn more child threads will + # raise "RuntimeError: can't create new thread at interpreter shutdown" + for thread in threads: + thread.join()