[bazel] Generate @bitstreams targets for MMI files

See issue #13603

Signed-off-by: Dan McArdle <dmcardle@google.com>
diff --git a/rules/scripts/BUILD b/rules/scripts/BUILD
index aa334d9..a3a03cb 100644
--- a/rules/scripts/BUILD
+++ b/rules/scripts/BUILD
@@ -2,7 +2,7 @@
 # Licensed under the Apache License, Version 2.0, see LICENSE for details.
 # SPDX-License-Identifier: Apache-2.0
 
-load("@rules_python//python:defs.bzl", "py_binary")
+load("@rules_python//python:defs.bzl", "py_test")
 
 package(default_visibility = ["//visibility:public"])
 
diff --git a/rules/scripts/bitstreams_workspace.py b/rules/scripts/bitstreams_workspace.py
index 3b2f6ab..48730ff 100755
--- a/rules/scripts/bitstreams_workspace.py
+++ b/rules/scripts/bitstreams_workspace.py
@@ -254,7 +254,7 @@
         (test_rom_file, ) = files_by_extension['orig']
         (mask_rom_file, ) = files_by_extension['splice']
 
-        return """# This file was autogenerated. Do not edit!
+        bazel_string = """# This file was autogenerated. Do not edit!
 # Built at {datetime}.
 # Configured for bitstream: {key}
 
@@ -276,6 +276,26 @@
            orig=test_rom_file,
            splice=mask_rom_file)
 
+        used_target_names: Set[str] = set()
+
+        for mmi_file in sorted(files_by_extension.get('mmi', set())):
+            target_name = os.path.basename(mmi_file).replace('.', '_')
+            if target_name in used_target_names:
+                logging.error(
+                    "Target name {} for file {} would collide with another target"
+                    .format(repr(target_name), repr(mmi_file)))
+                sys.exit(1)
+            used_target_names.add(target_name)
+
+            bazel_string += """
+filegroup(
+    name = "{target_name}",
+    srcs = ["{mmi_file}"],
+)
+""".format(target_name=target_name, mmi_file=mmi_file)
+
+        return bazel_string
+
     def WriteBuildFile(self, build_file: Path, key: str) -> str:
         """Write a BUILD file for the requested bitstream files.
 
diff --git a/rules/scripts/bitstreams_workspace_test.py b/rules/scripts/bitstreams_workspace_test.py
index 1617886..1d78f90 100644
--- a/rules/scripts/bitstreams_workspace_test.py
+++ b/rules/scripts/bitstreams_workspace_test.py
@@ -17,7 +17,8 @@
 
         MOCKED_OS_WALK_RETURN = [
             # os.walk() yields tuples of the form (root, dir, files).
-            ('cache/abcd', [], [BITSTREAM_ORIG, BITSTREAM_SPLICE]),
+            ('cache/abcd', [],
+             [BITSTREAM_ORIG, BITSTREAM_SPLICE, 'rom.mmi', 'otp.mmi']),
         ]
         os.walk = unittest.mock.MagicMock(name='os.walk',
                                           return_value=MOCKED_OS_WALK_RETURN)
@@ -39,6 +40,10 @@
                 'orig': set([os.path.join('cache', 'abcd', BITSTREAM_ORIG)]),
                 'splice': set(
                     [os.path.join('cache', 'abcd', BITSTREAM_SPLICE)]),
+                'mmi': {
+                    os.path.join('cache', 'abcd', 'rom.mmi'),
+                    os.path.join('cache', 'abcd', 'otp.mmi'),
+                },
             })
 
         os.walk.assert_called_once_with('cache/abcd')
@@ -49,11 +54,11 @@
 
         MOCKED_OS_WALK_RETURN = [
             # os.walk() yields tuples of the form (root, dir, files).
-            ('cache/abcd', [], [BITSTREAM_ORIG, BITSTREAM_SPLICE]),
+            ('cache/abcd', [],
+             [BITSTREAM_ORIG, BITSTREAM_SPLICE, 'rom.mmi', 'otp.mmi']),
         ]
         os.walk = unittest.mock.MagicMock(name='os.walk',
                                           return_value=MOCKED_OS_WALK_RETURN)
-        mocked_open = unittest.mock.mock_open()
 
         BitstreamCache._GetDateTimeStr = unittest.mock.MagicMock(
             name='BitstreamCache._GetDateTimeStr',
@@ -65,18 +70,10 @@
                                offline=True)
         cache.InitRepository = unittest.mock.MagicMock(name='method')
 
-        with unittest.mock.patch('builtins.open', mocked_open):
-            cache.WriteBuildFile('BUILD.mock', 'abcd')
-
-        # This is more of an implementation detail, but it verifies that we hit
-        # the the mocked `os.walk` function as expected.
-        os.walk.assert_called_once_with('cache/abcd')
-
-        mocked_open.assert_has_calls([
-            unittest.mock.call('BUILD.mock', 'wt'),
-            unittest.mock.call().__enter__(),
-            unittest.mock.call().write(
-                '''# This file was autogenerated. Do not edit!
+        bazel_string = cache._ConstructBazelString('BUILD.mock', 'abcd')
+        self.maxDiff = None
+        self.assertEqual(
+            bazel_string, '''# This file was autogenerated. Do not edit!
 # Built at 2022-07-14T15:02:54.463801.
 # Configured for bitstream: abcd
 
@@ -93,9 +90,21 @@
     name = "bitstream_mask_rom",
     srcs = ["cache/abcd/lowrisc_systems_chip_earlgrey_cw310_0.1.bit.splice"],
 )
-'''),
-            unittest.mock.call().__exit__(None, None, None),
-        ])
+
+filegroup(
+    name = "otp_mmi",
+    srcs = ["cache/abcd/otp.mmi"],
+)
+
+filegroup(
+    name = "rom_mmi",
+    srcs = ["cache/abcd/rom.mmi"],
+)
+''')
+
+        # This is more of an implementation detail, but it verifies that we hit
+        # the the mocked `os.walk` function as expected.
+        os.walk.assert_called_once_with('cache/abcd')
 
 
 if __name__ == '__main__':