From 791c86397a75144577cc1ee1ebb4eb65123bd1e7 Mon Sep 17 00:00:00 2001 From: andreas hilti Date: Mon, 20 May 2024 09:40:43 +0200 Subject: [PATCH 1/2] Add output version argument to merge command Signed-off-by: andreas hilti --- README.md | 1 + src/cyclonedx/Commands/MergeCommand.cs | 3 +- src/cyclonedx/Commands/MergeCommandOptions.cs | 5 +-- tests/cyclonedx.tests/MergeTests.cs | 28 +++++++++------- ...son_autodetect_sbom.json_autodetect_.snap} | 0 ...bom2.json_autodetect_sbom.json_json_.snap} | 0 ...json_autodetect_sbom.xml_autodetect_.snap} | 0 ..._sbom2.json_autodetect_sbom.xml_xml_.snap} | 0 ...bom2.json_json_sbom.json_autodetect_.snap} | 0 ...2.json_json_sbom.json_autodetect_v1_4.snap | 33 +++++++++++++++++++ ....xml_autodetect_sbom.xml_autodetect_.snap} | 0 ...xml_autodetect_sbom.json_autodetect_.snap} | 0 ....xml_autodetect_sbom.xml_autodetect_.snap} | 0 ...l_autodetect_sbom.xml_autodetect_v1_4.snap | 27 +++++++++++++++ ...l_sbom2.xml_xml_sbom.xml_autodetect_.snap} | 0 ...son_autodetect_sbom.json_autodetect_.snap} | 0 16 files changed, 82 insertions(+), 15 deletions(-) rename tests/cyclonedx.tests/__snapshots__/{MergeTests.Merge_Flat_sbom1.json_sbom2.json_autodetect_sbom.json_autodetect.snap => MergeTests.Merge_Flat_sbom1.json_sbom2.json_autodetect_sbom.json_autodetect_.snap} (100%) rename tests/cyclonedx.tests/__snapshots__/{MergeTests.Merge_Flat_sbom1.json_sbom2.json_autodetect_sbom.json_json.snap => MergeTests.Merge_Flat_sbom1.json_sbom2.json_autodetect_sbom.json_json_.snap} (100%) rename tests/cyclonedx.tests/__snapshots__/{MergeTests.Merge_Flat_sbom1.json_sbom2.json_autodetect_sbom.xml_autodetect.snap => MergeTests.Merge_Flat_sbom1.json_sbom2.json_autodetect_sbom.xml_autodetect_.snap} (100%) rename tests/cyclonedx.tests/__snapshots__/{MergeTests.Merge_Flat_sbom1.json_sbom2.json_autodetect_sbom.xml_xml.snap => MergeTests.Merge_Flat_sbom1.json_sbom2.json_autodetect_sbom.xml_xml_.snap} (100%) rename tests/cyclonedx.tests/__snapshots__/{MergeTests.Merge_Flat_sbom1.json_sbom2.json_json_sbom.json_autodetect.snap => MergeTests.Merge_Flat_sbom1.json_sbom2.json_json_sbom.json_autodetect_.snap} (100%) create mode 100644 tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Flat_sbom1.json_sbom2.json_json_sbom.json_autodetect_v1_4.snap rename tests/cyclonedx.tests/__snapshots__/{MergeTests.Merge_Flat_sbom1.json_sbom2.xml_autodetect_sbom.xml_autodetect.snap => MergeTests.Merge_Flat_sbom1.json_sbom2.xml_autodetect_sbom.xml_autodetect_.snap} (100%) rename tests/cyclonedx.tests/__snapshots__/{MergeTests.Merge_Flat_sbom1.xml_sbom2.xml_autodetect_sbom.json_autodetect.snap => MergeTests.Merge_Flat_sbom1.xml_sbom2.xml_autodetect_sbom.json_autodetect_.snap} (100%) rename tests/cyclonedx.tests/__snapshots__/{MergeTests.Merge_Flat_sbom1.xml_sbom2.xml_autodetect_sbom.xml_autodetect.snap => MergeTests.Merge_Flat_sbom1.xml_sbom2.xml_autodetect_sbom.xml_autodetect_.snap} (100%) create mode 100644 tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Flat_sbom1.xml_sbom2.xml_autodetect_sbom.xml_autodetect_v1_4.snap rename tests/cyclonedx.tests/__snapshots__/{MergeTests.Merge_Flat_sbom1.xml_sbom2.xml_xml_sbom.xml_autodetect.snap => MergeTests.Merge_Flat_sbom1.xml_sbom2.xml_xml_sbom.xml_autodetect_.snap} (100%) rename tests/cyclonedx.tests/__snapshots__/{MergeTests.Merge_Hierarchical_sbom1.json_sbom2.json_autodetect_sbom.json_autodetect.snap => MergeTests.Merge_Hierarchical_sbom1.json_sbom2.json_autodetect_sbom.json_autodetect_.snap} (100%) diff --git a/README.md b/README.md index 6a58089..c553494 100755 --- a/README.md +++ b/README.md @@ -196,6 +196,7 @@ Options: --output-file Output BOM filename, will write to stdout if no value provided. --input-format Specify input file format. --output-format Specify output file format. + --output-version Specify output BOM specification version. --hierarchical Perform a hierarchical merge. --group Provide the group of software the merged BOM describes. --name Provide the name of software the merged BOM describes (required for hierarchical merging). diff --git a/src/cyclonedx/Commands/MergeCommand.cs b/src/cyclonedx/Commands/MergeCommand.cs index 7a96404..d52a5b3 100644 --- a/src/cyclonedx/Commands/MergeCommand.cs +++ b/src/cyclonedx/Commands/MergeCommand.cs @@ -35,6 +35,7 @@ public static void Configure(RootCommand rootCommand) subCommand.Add(new Option("--output-file", "Output BOM filename, will write to stdout if no value provided.")); subCommand.Add(new Option("--input-format", "Specify input file format.")); subCommand.Add(new Option("--output-format", "Specify output file format.")); + subCommand.Add(new Option("--output-version", "Specify output BOM specification version.")); subCommand.Add(new Option("--hierarchical", "Perform a hierarchical merge.")); subCommand.Add(new Option("--group", "Provide the group of software the merged BOM describes.")); subCommand.Add(new Option("--name", "Provide the name of software the merged BOM describes (required for hierarchical merging).")); @@ -110,7 +111,7 @@ public static async Task Merge(MergeCommandOptions options) Console.WriteLine($" Total {outputBom.Components?.Count ?? 0} components"); } - return await CliUtils.OutputBomHelper(outputBom, options.OutputFormat, options.OutputFile).ConfigureAwait(false); + return await CliUtils.OutputBomHelper(outputBom, (ConvertFormat)options.OutputFormat, options.OutputVersion, options.OutputFile).ConfigureAwait(false); } private static async Task> InputBoms(IEnumerable inputFilenames, CycloneDXBomFormat inputFormat, bool outputToConsole) diff --git a/src/cyclonedx/Commands/MergeCommandOptions.cs b/src/cyclonedx/Commands/MergeCommandOptions.cs index f3078c4..0e177fb 100644 --- a/src/cyclonedx/Commands/MergeCommandOptions.cs +++ b/src/cyclonedx/Commands/MergeCommandOptions.cs @@ -1,4 +1,4 @@ -// This file is part of CycloneDX CLI Tool +// This file is part of CycloneDX CLI Tool // // Licensed under the Apache License, Version 2.0 (the “License”); // you may not use this file except in compliance with the License. @@ -24,9 +24,10 @@ public class MergeCommandOptions public string OutputFile { get; set; } public CycloneDXBomFormat InputFormat { get; set; } public CycloneDXBomFormat OutputFormat { get; set; } + public SpecificationVersion? OutputVersion { get; set; } public bool Hierarchical { get; set; } public string Group { get; set; } public string Name { get; set; } public string Version { get; set; } } -} \ No newline at end of file +} diff --git a/tests/cyclonedx.tests/MergeTests.cs b/tests/cyclonedx.tests/MergeTests.cs index 4a474b1..e9b60dc 100644 --- a/tests/cyclonedx.tests/MergeTests.cs +++ b/tests/cyclonedx.tests/MergeTests.cs @@ -1,4 +1,4 @@ -// This file is part of CycloneDX CLI Tool +// This file is part of CycloneDX CLI Tool // // Licensed under the Apache License, Version 2.0 (the “License”); // you may not use this file except in compliance with the License. @@ -29,20 +29,23 @@ namespace CycloneDX.Cli.Tests public class MergeTests { [Theory] - [InlineData(new string[] { "sbom1.json", "sbom2.json"}, CycloneDXBomFormat.autodetect, "sbom.json", CycloneDXBomFormat.autodetect, true, null, "Thing", "1")] - [InlineData(new string[] { "sbom1.json", "sbom2.json"}, CycloneDXBomFormat.autodetect, "sbom.json", CycloneDXBomFormat.autodetect, false, null, null, null)] - [InlineData(new string[] { "sbom1.json", "sbom2.json"}, CycloneDXBomFormat.autodetect, "sbom.xml", CycloneDXBomFormat.autodetect, false, null, null, null)] - [InlineData(new string[] { "sbom1.json", "sbom2.json"}, CycloneDXBomFormat.json, "sbom.json", CycloneDXBomFormat.autodetect, false, null, null, null)] - [InlineData(new string[] { "sbom1.xml", "sbom2.xml"}, CycloneDXBomFormat.autodetect, "sbom.xml", CycloneDXBomFormat.autodetect, false, null, null, null)] - [InlineData(new string[] { "sbom1.xml", "sbom2.xml"}, CycloneDXBomFormat.autodetect, "sbom.json", CycloneDXBomFormat.autodetect, false, null, null, null)] - [InlineData(new string[] { "sbom1.xml", "sbom2.xml"}, CycloneDXBomFormat.xml, "sbom.xml", CycloneDXBomFormat.autodetect, false, null, null, null)] - [InlineData(new string[] { "sbom1.json", "sbom2.xml"}, CycloneDXBomFormat.autodetect, "sbom.xml", CycloneDXBomFormat.autodetect, false, null, null, null)] - [InlineData(new string[] { "sbom1.json", "sbom2.json"}, CycloneDXBomFormat.autodetect, "sbom.json", CycloneDXBomFormat.json, false, null, null, null)] - [InlineData(new string[] { "sbom1.json", "sbom2.json"}, CycloneDXBomFormat.autodetect, "sbom.xml", CycloneDXBomFormat.xml, false, null, null, null)] + [InlineData(new[] { "sbom1.json", "sbom2.json"}, CycloneDXBomFormat.autodetect, "sbom.json", CycloneDXBomFormat.autodetect, null, true, null, "Thing", "1")] + [InlineData(new[] { "sbom1.json", "sbom2.json"}, CycloneDXBomFormat.autodetect, "sbom.json", CycloneDXBomFormat.autodetect, null, false, null, null, null)] + [InlineData(new[] { "sbom1.json", "sbom2.json"}, CycloneDXBomFormat.autodetect, "sbom.xml", CycloneDXBomFormat.autodetect, null, false, null, null, null)] + [InlineData(new[] { "sbom1.json", "sbom2.json"}, CycloneDXBomFormat.json, "sbom.json", CycloneDXBomFormat.autodetect, null, false, null, null, null)] + [InlineData(new[] { "sbom1.json", "sbom2.json" }, CycloneDXBomFormat.json, "sbom.json", CycloneDXBomFormat.autodetect, SpecificationVersion.v1_4, false, null, null, null)] + [InlineData(new[] { "sbom1.xml", "sbom2.xml"}, CycloneDXBomFormat.autodetect, "sbom.xml", CycloneDXBomFormat.autodetect, null, false, null, null, null)] + [InlineData(new[] { "sbom1.xml", "sbom2.xml" }, CycloneDXBomFormat.autodetect, "sbom.xml", CycloneDXBomFormat.autodetect, SpecificationVersion.v1_4, false, null, null, null)] + [InlineData(new[] { "sbom1.xml", "sbom2.xml"}, CycloneDXBomFormat.autodetect, "sbom.json", CycloneDXBomFormat.autodetect, null, false, null, null, null)] + [InlineData(new[] { "sbom1.xml", "sbom2.xml"}, CycloneDXBomFormat.xml, "sbom.xml", CycloneDXBomFormat.autodetect, null, false, null, null, null)] + [InlineData(new[] { "sbom1.json", "sbom2.xml"}, CycloneDXBomFormat.autodetect, "sbom.xml", CycloneDXBomFormat.autodetect, null, false, null, null, null)] + [InlineData(new[] { "sbom1.json", "sbom2.json"}, CycloneDXBomFormat.autodetect, "sbom.json", CycloneDXBomFormat.json, null, false, null, null, null)] + [InlineData(new[] { "sbom1.json", "sbom2.json"}, CycloneDXBomFormat.autodetect, "sbom.xml", CycloneDXBomFormat.xml, null, false, null, null, null)] public async Task Merge( string[] inputFilenames, CycloneDXBomFormat inputFormat, string outputFilename, CycloneDXBomFormat outputFormat, + SpecificationVersion? outputVersion, bool hierarchical, string group, string name, string version ) @@ -57,6 +60,7 @@ public async Task Merge( InputFormat = inputFormat, OutputFile = fullOutputPath, OutputFormat = outputFormat, + OutputVersion = outputVersion, Hierarchical = hierarchical, Group = group, Name = name, @@ -73,7 +77,7 @@ public async Task Merge( var bom = File.ReadAllText(fullOutputPath); bom = Regex.Replace(bom, @"\s*""serialNumber"": "".*?"",\r?\n", ""); // json bom = Regex.Replace(bom, @"\s+serialNumber="".*?""", ""); // xml - Snapshot.Match(bom, SnapshotNameExtension.Create(hierarchical ? "Hierarchical" : "Flat", snapshotInputFilenames, inputFormat, outputFilename, outputFormat)); + Snapshot.Match(bom, SnapshotNameExtension.Create(hierarchical ? "Hierarchical" : "Flat", snapshotInputFilenames, inputFormat, outputFilename, outputFormat, outputVersion)); } } } diff --git a/tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Flat_sbom1.json_sbom2.json_autodetect_sbom.json_autodetect.snap b/tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Flat_sbom1.json_sbom2.json_autodetect_sbom.json_autodetect_.snap similarity index 100% rename from tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Flat_sbom1.json_sbom2.json_autodetect_sbom.json_autodetect.snap rename to tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Flat_sbom1.json_sbom2.json_autodetect_sbom.json_autodetect_.snap diff --git a/tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Flat_sbom1.json_sbom2.json_autodetect_sbom.json_json.snap b/tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Flat_sbom1.json_sbom2.json_autodetect_sbom.json_json_.snap similarity index 100% rename from tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Flat_sbom1.json_sbom2.json_autodetect_sbom.json_json.snap rename to tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Flat_sbom1.json_sbom2.json_autodetect_sbom.json_json_.snap diff --git a/tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Flat_sbom1.json_sbom2.json_autodetect_sbom.xml_autodetect.snap b/tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Flat_sbom1.json_sbom2.json_autodetect_sbom.xml_autodetect_.snap similarity index 100% rename from tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Flat_sbom1.json_sbom2.json_autodetect_sbom.xml_autodetect.snap rename to tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Flat_sbom1.json_sbom2.json_autodetect_sbom.xml_autodetect_.snap diff --git a/tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Flat_sbom1.json_sbom2.json_autodetect_sbom.xml_xml.snap b/tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Flat_sbom1.json_sbom2.json_autodetect_sbom.xml_xml_.snap similarity index 100% rename from tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Flat_sbom1.json_sbom2.json_autodetect_sbom.xml_xml.snap rename to tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Flat_sbom1.json_sbom2.json_autodetect_sbom.xml_xml_.snap diff --git a/tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Flat_sbom1.json_sbom2.json_json_sbom.json_autodetect.snap b/tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Flat_sbom1.json_sbom2.json_json_sbom.json_autodetect_.snap similarity index 100% rename from tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Flat_sbom1.json_sbom2.json_json_sbom.json_autodetect.snap rename to tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Flat_sbom1.json_sbom2.json_json_sbom.json_autodetect_.snap diff --git a/tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Flat_sbom1.json_sbom2.json_json_sbom.json_autodetect_v1_4.snap b/tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Flat_sbom1.json_sbom2.json_json_sbom.json_autodetect_v1_4.snap new file mode 100644 index 0000000..31cf7f5 --- /dev/null +++ b/tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Flat_sbom1.json_sbom2.json_json_sbom.json_autodetect_v1_4.snap @@ -0,0 +1,33 @@ +{ + "bomFormat": "CycloneDX", + "specVersion": "1.4", "version": 1, + "metadata": { + "component": { + "type": "application", + "name": "thing1", + "version": "1" + } + }, + "components": [ + { + "type": "library", + "name": "acme-library", + "version": "1.0.0" + }, + { + "type": "application", + "name": "thing1", + "version": "1" + }, + { + "type": "framework", + "name": "acme-framework", + "version": "1.0.0" + }, + { + "type": "application", + "name": "thing2", + "version": "1" + } + ] +} diff --git a/tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Flat_sbom1.json_sbom2.xml_autodetect_sbom.xml_autodetect.snap b/tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Flat_sbom1.json_sbom2.xml_autodetect_sbom.xml_autodetect_.snap similarity index 100% rename from tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Flat_sbom1.json_sbom2.xml_autodetect_sbom.xml_autodetect.snap rename to tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Flat_sbom1.json_sbom2.xml_autodetect_sbom.xml_autodetect_.snap diff --git a/tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Flat_sbom1.xml_sbom2.xml_autodetect_sbom.json_autodetect.snap b/tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Flat_sbom1.xml_sbom2.xml_autodetect_sbom.json_autodetect_.snap similarity index 100% rename from tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Flat_sbom1.xml_sbom2.xml_autodetect_sbom.json_autodetect.snap rename to tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Flat_sbom1.xml_sbom2.xml_autodetect_sbom.json_autodetect_.snap diff --git a/tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Flat_sbom1.xml_sbom2.xml_autodetect_sbom.xml_autodetect.snap b/tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Flat_sbom1.xml_sbom2.xml_autodetect_sbom.xml_autodetect_.snap similarity index 100% rename from tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Flat_sbom1.xml_sbom2.xml_autodetect_sbom.xml_autodetect.snap rename to tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Flat_sbom1.xml_sbom2.xml_autodetect_sbom.xml_autodetect_.snap diff --git a/tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Flat_sbom1.xml_sbom2.xml_autodetect_sbom.xml_autodetect_v1_4.snap b/tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Flat_sbom1.xml_sbom2.xml_autodetect_sbom.xml_autodetect_v1_4.snap new file mode 100644 index 0000000..c3e019f --- /dev/null +++ b/tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Flat_sbom1.xml_sbom2.xml_autodetect_sbom.xml_autodetect_v1_4.snap @@ -0,0 +1,27 @@ + + + + + thing1 + 1 + + + + + acme-library + 1.0.0 + + + thing1 + 1 + + + acme-framework + 1.0.0 + + + thing2 + 1 + + + diff --git a/tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Flat_sbom1.xml_sbom2.xml_xml_sbom.xml_autodetect.snap b/tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Flat_sbom1.xml_sbom2.xml_xml_sbom.xml_autodetect_.snap similarity index 100% rename from tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Flat_sbom1.xml_sbom2.xml_xml_sbom.xml_autodetect.snap rename to tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Flat_sbom1.xml_sbom2.xml_xml_sbom.xml_autodetect_.snap diff --git a/tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Hierarchical_sbom1.json_sbom2.json_autodetect_sbom.json_autodetect.snap b/tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Hierarchical_sbom1.json_sbom2.json_autodetect_sbom.json_autodetect_.snap similarity index 100% rename from tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Hierarchical_sbom1.json_sbom2.json_autodetect_sbom.json_autodetect.snap rename to tests/cyclonedx.tests/__snapshots__/MergeTests.Merge_Hierarchical_sbom1.json_sbom2.json_autodetect_sbom.json_autodetect_.snap From b99123f95cea357dff099a06840d3c4dce05e1e8 Mon Sep 17 00:00:00 2001 From: andreas hilti Date: Sat, 15 Jun 2024 12:21:55 +0200 Subject: [PATCH 2/2] fix style issues Signed-off-by: andreas hilti --- .markdownlint.json | 5 +++++ README.md | 16 ++++++++-------- 2 files changed, 13 insertions(+), 8 deletions(-) create mode 100644 .markdownlint.json diff --git a/.markdownlint.json b/.markdownlint.json new file mode 100644 index 0000000..ce6da78 --- /dev/null +++ b/.markdownlint.json @@ -0,0 +1,5 @@ +{ + "MD013": { + "code_blocks": false + } +} \ No newline at end of file diff --git a/README.md b/README.md index c553494..1f9c360 100755 --- a/README.md +++ b/README.md @@ -192,15 +192,15 @@ Usage: cyclonedx merge [options] Options: - --input-files Input BOM filenames (separate filenames with a space). - --output-file Output BOM filename, will write to stdout if no value provided. - --input-format Specify input file format. - --output-format Specify output file format. + --input-files Input BOM filenames (separate filenames with a space). + --output-file Output BOM filename, will write to stdout if no value provided. + --input-format Specify input file format. + --output-format Specify output file format. --output-version Specify output BOM specification version. - --hierarchical Perform a hierarchical merge. - --group Provide the group of software the merged BOM describes. - --name Provide the name of software the merged BOM describes (required for hierarchical merging). - --version Provide the version of software the merged BOM describes (required for hierarchical merging). + --hierarchical Perform a hierarchical merge. + --group Provide the group of software the merged BOM describes. + --name Provide the name of software the merged BOM describes (required for hierarchical merging). + --version Provide the version of software the merged BOM describes (required for hierarchical merging). ``` Note: To perform a hierarchical merge all BOMs need the subject of the BOM