Use separate lib build paths for different configurations (#27694)

Libraries with different configurations use different directories in the
sysroot (e.g. wasm64-emscripten/pic) but currently all use the same
build directory across configurations. When using Ninja, these
intermediate directories persist and can result in corruption if one
configuration is built after another. We could solve this by just
clearing them, but keeping the build directories around can be useful
for local development, and will also allow building all the
configurations at once (in a future PR).

This PR just makes the build directory share the path logic with the lib
directory. (The ports system has its own separate logic).
diff --git a/.circleci/config.yml b/.circleci/config.yml
index 57a91ae..f9c9b99 100644
--- a/.circleci/config.yml
+++ b/.circleci/config.yml
@@ -232,7 +232,7 @@
             ls -l ~/cache/sysroot/include/emscripten/heap.h
             cat ~/cache/sysroot/lib/wasm32-emscripten/crtbegin-mt.o.ccache-log
             date
-            cat ~/cache/build/libclang_rt.builtins/absvdi2.o.ccache-log
+            cat ~/cache/build/wasm32-emscripten/libclang_rt.builtins/absvdi2.o.ccache-log
             ccache -s
             ccache --print-stats
             ccache -p
diff --git a/tools/cache.py b/tools/cache.py
index c90b0d3..c182943 100644
--- a/tools/cache.py
+++ b/tools/cache.py
@@ -109,15 +109,13 @@
   return str(Path(get_sysroot(absolute=True), *parts))
 
 
-def get_lib_dir(absolute):
+def get_lib_dir_relative():
   from .cmdline import options
 
-  ensure_setup()
-  path = Path(get_sysroot(absolute=absolute), 'lib')
   if settings.MEMORY64:
-    path = Path(path, 'wasm64-emscripten')
+    path = Path('wasm64-emscripten')
   else:
-    path = Path(path, 'wasm32-emscripten')
+    path = Path('wasm32-emscripten')
   # if relevant, use a subdir of the cache
   subdir = []
   if options.lto:
@@ -132,6 +130,11 @@
   return path
 
 
+def get_lib_dir(absolute):
+  ensure_setup()
+  return Path(get_sysroot(absolute=absolute), 'lib', get_lib_dir_relative())
+
+
 def get_lib_name(name, absolute=False):
   return str(get_lib_dir(absolute=absolute).joinpath(name))
 
diff --git a/tools/ports/__init__.py b/tools/ports/__init__.py
index 8ae3d8a..8195031 100644
--- a/tools/ports/__init__.py
+++ b/tools/ports/__init__.py
@@ -285,7 +285,7 @@
 
   @staticmethod
   def get_build_dir():
-    return system_libs.get_build_dir()
+    return cache.get_path('build')
 
   name_cache: set[str] = set()
 
diff --git a/tools/system_libs.py b/tools/system_libs.py
index 254ef06..f73efe1 100644
--- a/tools/system_libs.py
+++ b/tools/system_libs.py
@@ -15,6 +15,7 @@
 import textwrap
 from enum import IntEnum, auto
 from glob import iglob
+from pathlib import Path
 from time import time
 
 from . import building, cache, diagnostics, shared, utils
@@ -84,7 +85,7 @@
 
 
 def get_build_dir():
-  return cache.get_path('build')
+  return cache.get_path(Path('build', cache.get_lib_dir_relative()))
 
 
 def clean_env():
@@ -179,6 +180,7 @@
 def ensure_target_in_ninja_file(ninja_file, target):
   if os.path.isfile(ninja_file) and target in read_file(ninja_file):
     return
+  utils.safe_ensure_dirs(os.path.dirname(ninja_file))
   with open(ninja_file, 'a', encoding='utf-8') as f:
     f.write(target + '\n')