Repository: pypa/build

Issue:
0.4.0: --outdir fails when parent directory does not exist
```console
+ cd rich-10.4.0
+ /usr/bin/python3 -m build --no-isolation --outdir build/lib
Traceback (most recent call last):
  File "/usr/lib64/python3.8/runpy.py", line 194, in _run_module_as_main
    return _run_code(code, main_globals, None,
  File "/usr/lib64/python3.8/runpy.py", line 87, in _run_code
    exec(code, run_globals)
  File "/usr/lib/python3.8/site-packages/build/__main__.py", line 236, in <module>
    main(sys.argv[1:], 'python -m build')
  File "/usr/lib/python3.8/site-packages/build/__main__.py", line 228, in main
    build_package(args.srcdir, outdir, distributions, config_settings, not args.no_isolation, args.skip_dependency_check)
  File "/usr/lib/python3.8/site-packages/build/__main__.py", line 107, in build_package
    _build_in_current_env(builder, outdir, distributions, config_settings, skip_dependency_check)
  File "/usr/lib/python3.8/site-packages/build/__main__.py", line 84, in _build_in_current_env
    builder.build(dist, outdir, config_settings)
  File "/usr/lib/python3.8/site-packages/build/__init__.py", line 330, in build
    return self._call_backend(build, output_directory, config_settings, **kwargs)
  File "/usr/lib/python3.8/site-packages/build/__init__.py", line 340, in _call_backend
    os.mkdir(outdir)
FileNotFoundError: [Errno 2] No such file or directory: '/home/tkloczko/rpmbuild/BUILD/rich-10.4.0/build/lib'
```

Comments:

Patch:
diff --git a/CHANGELOG.rst b/CHANGELOG.rst
index dd43f31..9790e90 100644
--- a/CHANGELOG.rst
+++ b/CHANGELOG.rst
@@ -3,6 +3,17 @@ Changelog
 +++++++++
 
 
+
+Unreleased
+==========
+
+- Fix invoking the backend on an inexistent output directory with multiple levels (`PR #318`_, Fixes `#316`_)
+
+.. _PR #318: https://github.com/pypa/build/pull/318
+.. _#316: https://github.com/pypa/build/issues/316
+
+
+
 0.5.0 (19-06-2021)
 ==================
 
diff --git a/src/build/__init__.py b/src/build/__init__.py
index 11a045d..75ee7da 100644
--- a/src/build/__init__.py
+++ b/src/build/__init__.py
@@ -383,7 +383,7 @@ class ProjectBuilder(object):
             if not os.path.isdir(outdir):
                 raise BuildException("Build path '{}' exists and is not a directory".format(outdir))
         else:
-            os.mkdir(outdir)
+            os.makedirs(outdir)
 
         with self._handle_backend(hook_name):
             basename = callback(outdir, config_settings, **kwargs)  # type: str


Test patch:
diff --git a/tests/test_projectbuilder.py b/tests/test_projectbuilder.py
index e910765..a825410 100644
--- a/tests/test_projectbuilder.py
+++ b/tests/test_projectbuilder.py
@@ -467,6 +467,28 @@ def test_prepare_not_dir_outdir(mocker, tmp_dir, test_flit_path):
         builder.prepare('wheel', out)
 
 
+def test_no_outdir_single(mocker, tmp_dir, test_flit_path):
+    mocker.patch('pep517.wrappers.Pep517HookCaller.prepare_metadata_for_build_wheel', return_value='')
+
+    builder = build.ProjectBuilder(test_flit_path)
+
+    out = os.path.join(tmp_dir, 'out')
+    builder.prepare('wheel', out)
+
+    assert os.path.isdir(out)
+
+
+def test_no_outdir_multiple(mocker, tmp_dir, test_flit_path):
+    mocker.patch('pep517.wrappers.Pep517HookCaller.prepare_metadata_for_build_wheel', return_value='')
+
+    builder = build.ProjectBuilder(test_flit_path)
+
+    out = os.path.join(tmp_dir, 'does', 'not', 'exist')
+    builder.prepare('wheel', out)
+
+    assert os.path.isdir(out)
+
+
 def test_runner_user_specified(tmp_dir, test_flit_path):
     def dummy_runner(cmd, cwd=None, env=None):
         raise RuntimeError('Runner was called')