Repository: beeware/briefcase
Pull request #1864 (2024-06-08 01:59:07)

Store cookiecutter replay data in Briefcase template cache
Apparently, I'm blind....the option to pass a cookiecutter config by dict was there all along....

## PR Checklist:
<!--- Go over all the following points, and put an `x` in all the boxes that apply. -->
- [X] All new features have been tested
- [X] All new features have been documented
- [X] I have read the **CONTRIBUTING.md** file
- [X] I will abide by the code of conduct


Patch:
diff --git a/changes/1857.misc.rst b/changes/1857.misc.rst
new file mode 100644
index 00000000..4a5df59c
--- /dev/null
+++ b/changes/1857.misc.rst
@@ -0,0 +1,1 @@
+Cookiecutter now stores replay data in the Briefcase cache.
diff --git a/changes/933.removal.rst b/changes/933.removal.rst
index 3d26fa98..8618ce07 100644
--- a/changes/933.removal.rst
+++ b/changes/933.removal.rst
@@ -1,1 +1,1 @@
-Briefcase now uses a private cache of Cookiecutter templates, rather than the shared ``~/.cookiecutters`` directory. You can reclaim disk space by deleting ``~/.cookiecutters/briefcase-*`` (or the entire ``~/.cookiecutters`` folder if you are not using Cookiecutter for any other purposes).
+Briefcase now uses a private cache of Cookiecutter templates, rather than the shared ``~/.cookiecutters`` directory. You can reclaim disk space by deleting ``~/.cookiecutters/briefcase-*`` and ``~/.cookiecutter_replay/briefcase-*`` (or the entire ``~/.cookiecutters`` and ``~/.cookiecutter_replay`` folders if you are not using Cookiecutter for any other purposes).
diff --git a/src/briefcase/commands/base.py b/src/briefcase/commands/base.py
index 61638a4d..bae68f8d 100644
--- a/src/briefcase/commands/base.py
+++ b/src/briefcase/commands/base.py
@@ -1037,15 +1037,16 @@ Did you run Briefcase in a project directory that contains {filename.name!r}?"""
         self.logger.configure_stdlib_logging("cookiecutter")
 
         try:
-            # Unroll the template. Use a copy of the context to ensure that any
-            # mocked calls have an unmodified copy. Disable the use of replay.
+            # Unroll the template.
             self.tools.cookiecutter(
                 str(cached_template),
                 no_input=True,
                 output_dir=str(output_path),
                 checkout=branch,
+                # Use a copy to prevent changes propagating among tests while test suite is running
                 extra_context=extra_context.copy(),
-                replay=False,
+                # Store replay data in the Briefcase template cache instead of ~/.cookiecutter_replay
+                default_config={"replay_dir": str(self.template_cache_path(".replay"))},
             )
         except subprocess.CalledProcessError as e:
             # Computer is offline


Test patch:
diff --git a/tests/commands/convert/test_convert_app.py b/tests/commands/convert/test_convert_app.py
index ea840e80..5b574937 100644
--- a/tests/commands/convert/test_convert_app.py
+++ b/tests/commands/convert/test_convert_app.py
@@ -12,7 +12,10 @@ from briefcase.console import Console, Log
 @pytest.fixture
 def convert_command(tmp_path):
     return ConvertCommand(
-        base_path=tmp_path / "project", logger=Log(), console=Console()
+        base_path=tmp_path / "project",
+        data_path=tmp_path / "data",
+        logger=Log(),
+        console=Console(),
     )
 
 
@@ -76,7 +79,7 @@ def test_convert_app_unused_project_overrides(
             "briefcase_version": "37.42.7",
             "gui_framework": "None",
         },
-        replay=False,
+        default_config={"replay_dir": str(tmp_path / "data/templates/.replay")},
     )
     convert_command.migrate_necessary_files.assert_called_once_with(
         tmp_path / "working" / app_context["app_name"],
diff --git a/tests/commands/create/test_generate_app_template.py b/tests/commands/create/test_generate_app_template.py
index 9a6fb080..9a4a508f 100644
--- a/tests/commands/create/test_generate_app_template.py
+++ b/tests/commands/create/test_generate_app_template.py
@@ -103,7 +103,7 @@ def test_default_template(
         checkout=expected_branch,
         output_dir=str(tmp_path / "base_path/build/my-app/tester"),
         extra_context=full_context,
-        replay=False,
+        default_config={"replay_dir": str(tmp_path / "data/templates/.replay")},
     )
 
 
@@ -154,7 +154,7 @@ def test_default_template_dev(
             checkout="v37.42.7",
             output_dir=os.fsdecode(tmp_path / "base_path/build/my-app/tester"),
             extra_context=full_context,
-            replay=False,
+            default_config={"replay_dir": str(tmp_path / "data/templates/.replay")},
         ),
         mock.call(
             str(create_command.data_path / "templates/briefcase-Tester-Dummy-template"),
@@ -162,7 +162,7 @@ def test_default_template_dev(
             checkout="main",
             output_dir=os.fsdecode(tmp_path / "base_path/build/my-app/tester"),
             extra_context=full_context2,
-            replay=False,
+            default_config={"replay_dir": str(tmp_path / "data/templates/.replay")},
         ),
     ]
 
@@ -204,7 +204,7 @@ def test_default_template_dev_explicit_branch(
             checkout=branch,
             output_dir=os.fsdecode(tmp_path / "base_path/build/my-app/tester"),
             extra_context=full_context,
-            replay=False,
+            default_config={"replay_dir": str(tmp_path / "data/templates/.replay")},
         ),
     ]
 
@@ -253,7 +253,7 @@ def test_default_template_dev_explicit_invalid_branch(
             checkout=branch,
             output_dir=os.fsdecode(tmp_path / "base_path/build/my-app/tester"),
             extra_context=full_context,
-            replay=False,
+            default_config={"replay_dir": str(tmp_path / "data/templates/.replay")},
         ),
     ]
 
@@ -283,7 +283,7 @@ def test_explicit_branch(monkeypatch, create_command, myapp, full_context, tmp_p
         checkout=branch,
         output_dir=os.fsdecode(tmp_path / "base_path/build/my-app/tester"),
         extra_context=full_context,
-        replay=False,
+        default_config={"replay_dir": str(tmp_path / "data/templates/.replay")},
     )
 
 
@@ -311,7 +311,7 @@ def test_platform_exists(monkeypatch, create_command, myapp, full_context, tmp_p
         checkout="v37.42.7",
         output_dir=os.fsdecode(tmp_path / "base_path/build/my-app/tester"),
         extra_context=full_context,
-        replay=False,
+        default_config={"replay_dir": str(tmp_path / "data/templates/.replay")},
     )
 
 
@@ -344,7 +344,7 @@ def test_explicit_repo_template(
         checkout="v37.42.7",
         output_dir=os.fsdecode(tmp_path / "base_path/build/my-app/tester"),
         extra_context=full_context,
-        replay=False,
+        default_config={"replay_dir": str(tmp_path / "data/templates/.replay")},
     )
 
 
@@ -381,7 +381,7 @@ def test_explicit_repo_template_and_branch(
         checkout=branch,
         output_dir=os.fsdecode(tmp_path / "base_path/build/my-app/tester"),
         extra_context=full_context,
-        replay=False,
+        default_config={"replay_dir": str(tmp_path / "data/templates/.replay")},
     )
 
 
@@ -412,7 +412,7 @@ def test_explicit_local_template(
         checkout="v37.42.7",
         output_dir=os.fsdecode(tmp_path / "base_path/build/my-app/tester"),
         extra_context=full_context,
-        replay=False,
+        default_config={"replay_dir": str(tmp_path / "data/templates/.replay")},
     )
 
     # The template is a local directory, so there won't be any calls on git.
@@ -450,7 +450,7 @@ def test_explicit_local_template_and_branch(
         checkout=branch,
         output_dir=os.fsdecode(tmp_path / "base_path/build/my-app/tester"),
         extra_context=full_context,
-        replay=False,
+        default_config={"replay_dir": str(tmp_path / "data/templates/.replay")},
     )
 
     # The template is a local directory, so there won't be any calls on git.
@@ -494,7 +494,7 @@ def test_offline_repo_template(
         checkout="v37.42.7",
         output_dir=os.fsdecode(tmp_path / "base_path/build/my-app/tester"),
         extra_context=full_context,
-        replay=False,
+        default_config={"replay_dir": str(tmp_path / "data/templates/.replay")},
     )
 
 
@@ -567,7 +567,7 @@ def test_cached_template(monkeypatch, create_command, myapp, full_context, tmp_p
         checkout="v37.42.7",
         output_dir=os.fsdecode(tmp_path / "base_path/build/my-app/tester"),
         extra_context=full_context,
-        replay=False,
+        default_config={"replay_dir": str(tmp_path / "data/templates/.replay")},
     )
 
 
@@ -624,7 +624,7 @@ def test_cached_template_offline(
         checkout="v37.42.7",
         output_dir=str(tmp_path / "base_path/build/my-app/tester"),
         extra_context=full_context,
-        replay=False,
+        default_config={"replay_dir": str(tmp_path / "data/templates/.replay")},
     )
 
 
@@ -720,5 +720,5 @@ def test_x_permissions(
         checkout="v37.42.7",
         output_dir=os.fsdecode(tmp_path / "base_path/build/my-app/tester"),
         extra_context=full_context,
-        replay=False,
+        default_config={"replay_dir": str(tmp_path / "data/templates/.replay")},
     )
diff --git a/tests/commands/new/test_new_app.py b/tests/commands/new/test_new_app.py
index 5a12cf65..1748a6b0 100644
--- a/tests/commands/new/test_new_app.py
+++ b/tests/commands/new/test_new_app.py
@@ -17,7 +17,12 @@ from briefcase.exceptions import (
 
 @pytest.fixture
 def new_command(tmp_path):
-    return NewCommand(base_path=tmp_path, logger=Log(), console=Console())
+    return NewCommand(
+        base_path=tmp_path / "base",
+        data_path=tmp_path / "data",
+        logger=Log(),
+        console=Console(),
+    )
 
 
 @pytest.mark.parametrize(
@@ -73,7 +78,7 @@ def test_new_app(
     new_command.tools.cookiecutter.assert_called_once_with(
         "~/.cookiecutters/briefcase-template",
         no_input=True,
-        output_dir=os.fsdecode(tmp_path),
+        output_dir=os.fsdecode(tmp_path / "base"),
         checkout=expected_branch,
         extra_context={
             "formal_name": "My Application",
@@ -88,7 +93,7 @@ def test_new_app(
             "app_source": "main()",
             "pyproject_requires": "toga",
         },
-        replay=False,
+        default_config={"replay_dir": str(tmp_path / "data/templates/.replay")},
     )
 
 
@@ -135,7 +140,7 @@ def test_new_app_missing_template(monkeypatch, new_command, tmp_path):
     new_command.tools.cookiecutter.assert_called_once_with(
         "~/.cookiecutters/briefcase-template",
         no_input=True,
-        output_dir=os.fsdecode(tmp_path),
+        output_dir=os.fsdecode(tmp_path / "base"),
         checkout="v37.42.7",
         extra_context={
             "formal_name": "My Application",
@@ -150,7 +155,7 @@ def test_new_app_missing_template(monkeypatch, new_command, tmp_path):
             "app_source": "main()",
             "pyproject_requires": "toga",
         },
-        replay=False,
+        default_config={"replay_dir": str(tmp_path / "data/templates/.replay")},
     )
 
 
@@ -211,7 +216,7 @@ def test_new_app_dev(monkeypatch, new_command, tmp_path, briefcase_version):
             mock.call(
                 "~/.cookiecutters/briefcase-template",
                 no_input=True,
-                output_dir=os.fsdecode(tmp_path),
+                output_dir=os.fsdecode(tmp_path / "base"),
                 checkout="v37.42.7",
                 extra_context={
                     "formal_name": "My Application",
@@ -225,12 +230,12 @@ def test_new_app_dev(monkeypatch, new_command, tmp_path, briefcase_version):
                     "app_source": "main()",
                     "pyproject_requires": "toga",
                 },
-                replay=False,
+                default_config={"replay_dir": str(tmp_path / "data/templates/.replay")},
             ),
             mock.call(
                 "~/.cookiecutters/briefcase-template",
                 no_input=True,
-                output_dir=os.fsdecode(tmp_path),
+                output_dir=os.fsdecode(tmp_path / "base"),
                 checkout="main",
                 extra_context={
                     "formal_name": "My Application",
@@ -244,7 +249,7 @@ def test_new_app_dev(monkeypatch, new_command, tmp_path, briefcase_version):
                     "app_source": "main()",
                     "pyproject_requires": "toga",
                 },
-                replay=False,
+                default_config={"replay_dir": str(tmp_path / "data/templates/.replay")},
             ),
         ]
     )
@@ -286,7 +291,7 @@ def test_new_app_with_template(monkeypatch, new_command, tmp_path):
     new_command.tools.cookiecutter.assert_called_once_with(
         "https://example.com/other.git",
         no_input=True,
-        output_dir=os.fsdecode(tmp_path),
+        output_dir=os.fsdecode(tmp_path / "base"),
         checkout="v37.42.7",
         extra_context={
             "formal_name": "My Application",
@@ -301,7 +306,7 @@ def test_new_app_with_template(monkeypatch, new_command, tmp_path):
             "app_source": "main()",
             "pyproject_requires": "toga",
         },
-        replay=False,
+        default_config={"replay_dir": str(tmp_path / "data/templates/.replay")},
     )
 
 
@@ -346,7 +351,7 @@ def test_new_app_with_invalid_template(monkeypatch, new_command, tmp_path):
     new_command.tools.cookiecutter.assert_called_once_with(
         "https://example.com/other.git",
         no_input=True,
-        output_dir=os.fsdecode(tmp_path),
+        output_dir=os.fsdecode(tmp_path / "base"),
         checkout="v37.42.7",
         extra_context={
             "formal_name": "My Application",
@@ -361,7 +366,7 @@ def test_new_app_with_invalid_template(monkeypatch, new_command, tmp_path):
             "app_source": "main()",
             "pyproject_requires": "toga",
         },
-        replay=False,
+        default_config={"replay_dir": str(tmp_path / "data/templates/.replay")},
     )
 
 
@@ -409,7 +414,7 @@ def test_new_app_with_invalid_template_branch(monkeypatch, new_command, tmp_path
     new_command.tools.cookiecutter.assert_called_once_with(
         "https://example.com/other.git",
         no_input=True,
-        output_dir=os.fsdecode(tmp_path),
+        output_dir=os.fsdecode(tmp_path / "base"),
         checkout="v37.42.7",
         extra_context={
             "formal_name": "My Application",
@@ -424,7 +429,7 @@ def test_new_app_with_invalid_template_branch(monkeypatch, new_command, tmp_path
             "app_source": "main()",
             "pyproject_requires": "toga",
         },
-        replay=False,
+        default_config={"replay_dir": str(tmp_path / "data/templates/.replay")},
     )
 
 
@@ -463,7 +468,7 @@ def test_new_app_with_branch(monkeypatch, new_command, tmp_path):
     new_command.tools.cookiecutter.assert_called_once_with(
         "https://example.com/other.git",
         no_input=True,
-        output_dir=os.fsdecode(tmp_path),
+        output_dir=os.fsdecode(tmp_path / "base"),
         checkout="experimental",
         extra_context={
             "formal_name": "My Application",
@@ -478,7 +483,7 @@ def test_new_app_with_branch(monkeypatch, new_command, tmp_path):
             "app_source": "main()",
             "pyproject_requires": "toga",
         },
-        replay=False,
+        default_config={"replay_dir": str(tmp_path / "data/templates/.replay")},
     )
 
 
@@ -524,7 +529,7 @@ def test_new_app_unused_project_overrides(
     new_command.tools.cookiecutter.assert_called_once_with(
         "~/.cookiecutters/briefcase-template",
         no_input=True,
-        output_dir=os.fsdecode(tmp_path),
+        output_dir=os.fsdecode(tmp_path / "base"),
         checkout="v37.42.7",
         extra_context={
             "formal_name": "My Application",
@@ -539,7 +544,7 @@ def test_new_app_unused_project_overrides(
             "app_source": "main()",
             "pyproject_requires": "toga",
         },
-        replay=False,
+        default_config={"replay_dir": str(tmp_path / "data/templates/.replay")},
     )
 
     unused_project_override_warning = (
@@ -554,7 +559,7 @@ def test_abort_if_directory_exists(monkeypatch, new_command, tmp_path):
     monkeypatch.setattr(briefcase, "__version__", "37.42.7")
 
     # Create a colliding app name.
-    (tmp_path / "myapplication").mkdir()
+    (tmp_path / "base/myapplication").mkdir(parents=True)
 
     app_context = {
         "formal_name": "My Application",