diff --git a/CHANGELOG.md b/CHANGELOG.md index 1bbfa1b2..f759d369 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -18,6 +18,7 @@ and this project adheres to [Semantic Versioning](https://semver.org/spec/v2.0.0 * `basilisp.core/str` now delegates to the builtin Python `str` in all cases except for customizing the string output for builtin Python types (#1237) * Optimised mainstream seq-consuming functions by coercing their inputs into `seq` upfront (#1234) * Renamed `awith` and `afor` to `with-async` and `for-async` for improved clarity (#1248) + * `basilisp.main.init` will only initialize the runtime environment on the first invocation (#1242) ### Fixed * Fix a bug where protocols with methods with leading hyphens in the could not be defined (#1230) diff --git a/docs/gettingstarted.rst b/docs/gettingstarted.rst index 4adb7d41..3ee9c87e 100644 --- a/docs/gettingstarted.rst +++ b/docs/gettingstarted.rst @@ -120,8 +120,16 @@ Given a Basilisp entrypoint function ``main`` (taking no arguments) in the ``pro If you were to place this in a module such as ``myproject.main``, you could easily configure a `setuptools entry point `_ (or any analog with another build tool) to point to that script directly, effectively launching you directly to Basilisp code. For more sophisticated projects which may not have a direct or wrappable entrypoint, you can initialize the Basilisp runtime directly by calling :py:func:`basilisp.main.init` with no arguments. -This may be a better fit for a project using something like Django, where the entrypoint is dictated by Django. -In that case, you could use a hook such as Django's ``AppConfig.ready()``. +A natural placement for this function call would be in the root ``__init__.py`` for a package, where you can freely import and initialize Basilisp. + +.. code-block:: python + + import basilisp.main + + basilisp.main.init() + +You could also initialize Basilisp in a framework such as Django, where the entrypoint is dictated by the framework. +For example, you could use a hook such as Django's ``AppConfig.ready()``. .. code-block:: python diff --git a/src/basilisp/main.py b/src/basilisp/main.py index ea436d01..542d8560 100644 --- a/src/basilisp/main.py +++ b/src/basilisp/main.py @@ -1,6 +1,7 @@ import importlib import os import sysconfig +import threading from pathlib import Path from typing import Optional @@ -11,8 +12,11 @@ from basilisp.lang.typing import CompilerOpts from basilisp.lang.util import munge +_INIT_LOCK = threading.Lock() +_runtime_is_initialized = False -def init(opts: Optional[CompilerOpts] = None) -> None: + +def init(opts: Optional[CompilerOpts] = None, force_reload: bool = False) -> None: """ Initialize the runtime environment for Basilisp code evaluation. @@ -22,12 +26,22 @@ def init(opts: Optional[CompilerOpts] = None) -> None: If you want to execute a Basilisp file which is stored in a well-formed package or module structure, you probably want to use :py:func:`bootstrap`. + + ``init()`` may be called more than once. Only the first invocation will initialize + the runtime unless ``force_reload=True``. """ - logconfig.configure_root_logger() - runtime.init_ns_var() - runtime.bootstrap_core(opts if opts is not None else compiler_opts()) - importer.hook_imports() - importlib.import_module("basilisp.core") + global _runtime_is_initialized + + with _INIT_LOCK: + if _runtime_is_initialized and not force_reload: + return + + logconfig.configure_root_logger() + runtime.init_ns_var() + runtime.bootstrap_core(opts if opts is not None else compiler_opts()) + importer.hook_imports() + importlib.import_module("basilisp.core") + _runtime_is_initialized = True def bootstrap( diff --git a/tests/basilisp/cli_test.py b/tests/basilisp/cli_test.py index 70724554..53a04bf9 100644 --- a/tests/basilisp/cli_test.py +++ b/tests/basilisp/cli_test.py @@ -83,6 +83,7 @@ class CapturedIO: @pytest.fixture def run_cli(monkeypatch, capsys, cap_lisp_io): def _run_cli(args: Sequence[str], input: Optional[str] = None): + monkeypatch.setattr("basilisp.main._runtime_is_initialized", False) if input is not None: monkeypatch.setattr( "sys.stdin", io.TextIOWrapper(io.BytesIO(input.encode("utf-8")))