Add all accepted target environments to the tools' help texts (#2687)

Several tools take a --target-env option to specify the SPIR-V
environment to use. They all use spvParseTargetEnv to parse
the user-specified string and select the appropriate spv_target_env
but all tools list only _some_ of the valid values in their help
text.

This change makes the help text construction automatic from the
full list of valid values, establishing a single source of truth
for the values printed in the help text. The new utility function
added allows its user to specify padding and wrapping constraints
so the produced strings fits well in the various help texts.

Signed-off-by: Kévin Petit <kpet@free.fr>
diff --git a/source/spirv_target_env.cpp b/source/spirv_target_env.cpp
index 09a1219..b9681ed 100644
--- a/source/spirv_target_env.cpp
+++ b/source/spirv_target_env.cpp
@@ -15,6 +15,7 @@
 #include "source/spirv_target_env.h"
 
 #include <cstring>
+#include <map>
 #include <string>
 
 #include "source/spirv_constant.h"
@@ -103,80 +104,44 @@
   return SPV_SPIRV_VERSION_WORD(0, 0);
 }
 
+static const std::map<std::string, spv_target_env> spvTargetEnvNameMap = {
+    {"vulkan1.1spv1.4", SPV_ENV_VULKAN_1_1_SPIRV_1_4},
+    {"vulkan1.0", SPV_ENV_VULKAN_1_0},
+    {"vulkan1.1", SPV_ENV_VULKAN_1_1},
+    {"spv1.0", SPV_ENV_UNIVERSAL_1_0},
+    {"spv1.1", SPV_ENV_UNIVERSAL_1_1},
+    {"spv1.2", SPV_ENV_UNIVERSAL_1_2},
+    {"spv1.3", SPV_ENV_UNIVERSAL_1_3},
+    {"spv1.4", SPV_ENV_UNIVERSAL_1_4},
+    {"opencl1.2embedded", SPV_ENV_OPENCL_EMBEDDED_1_2},
+    {"opencl1.2", SPV_ENV_OPENCL_1_2},
+    {"opencl2.0embedded", SPV_ENV_OPENCL_EMBEDDED_2_0},
+    {"opencl2.0", SPV_ENV_OPENCL_2_0},
+    {"opencl2.1embedded", SPV_ENV_OPENCL_EMBEDDED_2_1},
+    {"opencl2.1", SPV_ENV_OPENCL_2_1},
+    {"opencl2.2embedded", SPV_ENV_OPENCL_EMBEDDED_2_2},
+    {"opencl2.2", SPV_ENV_OPENCL_2_2},
+    {"opengl4.0", SPV_ENV_OPENGL_4_0},
+    {"opengl4.1", SPV_ENV_OPENGL_4_1},
+    {"opengl4.2", SPV_ENV_OPENGL_4_2},
+    {"opengl4.3", SPV_ENV_OPENGL_4_3},
+    {"opengl4.5", SPV_ENV_OPENGL_4_5},
+    {"webgpu0", SPV_ENV_WEBGPU_0},
+};
+
 bool spvParseTargetEnv(const char* s, spv_target_env* env) {
-  auto match = [s](const char* b) {
-    return s && (0 == strncmp(s, b, strlen(b)));
-  };
-  if (match("vulkan1.1spv1.4")) {
-    if (env) *env = SPV_ENV_VULKAN_1_1_SPIRV_1_4;
-    return true;
-  } else if (match("vulkan1.0")) {
-    if (env) *env = SPV_ENV_VULKAN_1_0;
-    return true;
-  } else if (match("vulkan1.1")) {
-    if (env) *env = SPV_ENV_VULKAN_1_1;
-    return true;
-  } else if (match("spv1.0")) {
-    if (env) *env = SPV_ENV_UNIVERSAL_1_0;
-    return true;
-  } else if (match("spv1.1")) {
-    if (env) *env = SPV_ENV_UNIVERSAL_1_1;
-    return true;
-  } else if (match("spv1.2")) {
-    if (env) *env = SPV_ENV_UNIVERSAL_1_2;
-    return true;
-  } else if (match("spv1.3")) {
-    if (env) *env = SPV_ENV_UNIVERSAL_1_3;
-    return true;
-  } else if (match("spv1.4")) {
-    if (env) *env = SPV_ENV_UNIVERSAL_1_4;
-    return true;
-  } else if (match("opencl1.2embedded")) {
-    if (env) *env = SPV_ENV_OPENCL_EMBEDDED_1_2;
-    return true;
-  } else if (match("opencl1.2")) {
-    if (env) *env = SPV_ENV_OPENCL_1_2;
-    return true;
-  } else if (match("opencl2.0embedded")) {
-    if (env) *env = SPV_ENV_OPENCL_EMBEDDED_2_0;
-    return true;
-  } else if (match("opencl2.0")) {
-    if (env) *env = SPV_ENV_OPENCL_2_0;
-    return true;
-  } else if (match("opencl2.1embedded")) {
-    if (env) *env = SPV_ENV_OPENCL_EMBEDDED_2_1;
-    return true;
-  } else if (match("opencl2.1")) {
-    if (env) *env = SPV_ENV_OPENCL_2_1;
-    return true;
-  } else if (match("opencl2.2embedded")) {
-    if (env) *env = SPV_ENV_OPENCL_EMBEDDED_2_2;
-    return true;
-  } else if (match("opencl2.2")) {
-    if (env) *env = SPV_ENV_OPENCL_2_2;
-    return true;
-  } else if (match("opengl4.0")) {
-    if (env) *env = SPV_ENV_OPENGL_4_0;
-    return true;
-  } else if (match("opengl4.1")) {
-    if (env) *env = SPV_ENV_OPENGL_4_1;
-    return true;
-  } else if (match("opengl4.2")) {
-    if (env) *env = SPV_ENV_OPENGL_4_2;
-    return true;
-  } else if (match("opengl4.3")) {
-    if (env) *env = SPV_ENV_OPENGL_4_3;
-    return true;
-  } else if (match("opengl4.5")) {
-    if (env) *env = SPV_ENV_OPENGL_4_5;
-    return true;
-  } else if (match("webgpu0")) {
-    if (env) *env = SPV_ENV_WEBGPU_0;
-    return true;
-  } else {
-    if (env) *env = SPV_ENV_UNIVERSAL_1_0;
-    return false;
+  std::string envstr;
+  if (s != nullptr) {
+    envstr = s;
   }
+  if (spvTargetEnvNameMap.count(envstr) != 0) {
+    if (env) {
+      *env = spvTargetEnvNameMap.at(envstr);
+    }
+    return true;
+  }
+  if (env) *env = SPV_ENV_UNIVERSAL_1_0;
+  return false;
 }
 
 bool spvIsVulkanEnv(spv_target_env env) {
@@ -310,3 +275,29 @@
   }
   return "Unknown";
 }
+
+std::string spvTargetEnvList(const int pad, const int wrap) {
+  std::string ret;
+  size_t max_line_len = wrap - pad;  // The first line isn't padded
+  std::string line;
+  std::string sep = "";
+
+  for (auto& env_name : spvTargetEnvNameMap) {
+    std::string word = sep + env_name.first;
+    if (line.length() + word.length() > max_line_len) {
+      // Adding one word wouldn't fit, commit the line in progress and
+      // start a new one.
+      ret += line + "\n";
+      line.assign(pad, ' ');
+      // The first line is done. The max length now comprises the
+      // padding.
+      max_line_len = wrap;
+    }
+    line += word;
+    sep = "|";
+  }
+
+  ret += line;
+
+  return ret;
+}
diff --git a/source/spirv_target_env.h b/source/spirv_target_env.h
index c19d169..3e33a35 100644
--- a/source/spirv_target_env.h
+++ b/source/spirv_target_env.h
@@ -38,4 +38,12 @@
 // environment, i.e. "Vulkan", "WebGPU", "OpenCL", etc.
 std::string spvLogStringForEnv(spv_target_env env);
 
+// Returns a formatted list of all SPIR-V target environment names that
+// can be parsed by spvParseTargetEnv.
+// |pad| is the number of space characters that the begining of each line
+//       except the first one will be padded with.
+// |wrap| is the max length of lines the user desires. Word-wrapping will
+//        occur to satisfy this limit.
+std::string spvTargetEnvList(const int pad, const int wrap);
+
 #endif  // SOURCE_SPIRV_TARGET_ENV_H_
diff --git a/tools/as/as.cpp b/tools/as/as.cpp
index 1568579..acef5b4 100644
--- a/tools/as/as.cpp
+++ b/tools/as/as.cpp
@@ -21,6 +21,7 @@
 #include "tools/io.h"
 
 void print_usage(char* argv0) {
+  std::string target_env_list = spvTargetEnvList(19, 80);
   printf(
       R"(%s - Create a SPIR-V binary module from SPIR-V assembly text
 
@@ -41,11 +42,10 @@
                   Numeric IDs in the binary will have the same values as in the
                   source. Non-numeric IDs are allocated by filling in the gaps,
                   starting with 1 and going up.
-  --target-env {vulkan1.0|vulkan1.1|spv1.0|spv1.1|spv1.2|spv1.3|spv1.4}
-                  Use Vulkan 1.0, Vulkan 1.1, SPIR-V 1.0, SPIR-V 1.1,
-                  SPIR-V 1.2, SPIR-V 1.3, or SPIR-V 1.4
+  --target-env    {%s}
+                  Use specified environment.
 )",
-      argv0, argv0);
+      argv0, argv0, target_env_list.c_str());
 }
 
 static const auto kDefaultEnvironment = SPV_ENV_UNIVERSAL_1_4;
diff --git a/tools/link/linker.cpp b/tools/link/linker.cpp
index 3508b13..82d430e 100644
--- a/tools/link/linker.cpp
+++ b/tools/link/linker.cpp
@@ -23,6 +23,7 @@
 #include "tools/io.h"
 
 void print_usage(char* argv0) {
+  std::string target_env_list = spvTargetEnvList(27, 95);
   printf(
       R"(%s - Link SPIR-V binary files together.
 
@@ -39,11 +40,10 @@
   --allow-partial-linkage Allow partial linkage by accepting imported symbols to be unresolved.
   --verify-ids            Verify that IDs in the resulting modules are truly unique.
   --version               Display linker version information
-  --target-env            {vulkan1.0|vulkan1.1|spv1.0|spv1.1|spv1.2|spv1.3|spv1.4|opencl2.1|opencl2.2}
-                          Use Vulkan 1.0, Vulkan 1.1, SPIR-V 1.0, SPIR-V 1.1, SPIR-V 1.2, SPIR-V 1.3,
-                          SPIR-V1.4, OpenCL 2.1, OpenCL 2.2 validation rules.
+  --target-env            {%s}
+                          Use validation rules from the specified environment.
 )",
-      argv0, argv0);
+      argv0, argv0, target_env_list.c_str());
 }
 
 int main(int argc, char** argv) {
diff --git a/tools/opt/opt.cpp b/tools/opt/opt.cpp
index 585952b..af527e3 100644
--- a/tools/opt/opt.cpp
+++ b/tools/opt/opt.cpp
@@ -92,6 +92,7 @@
 }
 
 void PrintUsage(const char* program) {
+  std::string target_env_list = spvTargetEnvList(16, 80);
   // NOTE: Please maintain flags in lexicographical order.
   printf(
       R"(%s - Optimize a SPIR-V binary file.
@@ -436,9 +437,9 @@
   printf(R"(
   --target-env=<env>
                Set the target environment. Without this flag the target
-               enviroment defaults to spv1.3.
-               <env> must be one of vulkan1.0, vulkan1.1, opencl2.2, spv1.0,
-               spv1.1, spv1.2, spv1.3, or webgpu0.)");
+               enviroment defaults to spv1.3. <env> must be one of
+               {%s})",
+         target_env_list.c_str());
   printf(R"(
   --time-report
                Print the resource utilization of each pass (e.g., CPU time,
diff --git a/tools/val/val.cpp b/tools/val/val.cpp
index a61f4d1..6a8542d 100644
--- a/tools/val/val.cpp
+++ b/tools/val/val.cpp
@@ -25,6 +25,7 @@
 #include "tools/util/cli_consumer.h"
 
 void print_usage(char* argv0) {
+  std::string target_env_list = spvTargetEnvList(36, 105);
   printf(
       R"(%s - Validate a SPIR-V binary file.
 
@@ -65,13 +66,10 @@
   --before-hlsl-legalization       Allows code patterns that are intended to be
                                    fixed by spirv-opt's legalization passes.
   --version                        Display validator version information.
-  --target-env                     {vulkan1.0|vulkan1.1|vulkan1.1spv1.4|opencl2.2|spv1.0|spv1.1|
-                                    spv1.2|spv1.3|spv1.4|webgpu0}
-                                   Use Vulkan 1.0, Vulkan 1.1, Vulkan 1.1 with SPIR-V 1.4,
-                                   OpenCL 2.2, SPIR-V 1.0, SPIR-V 1.1, SPIR-V 1.2, SPIR-V 1.3,
-                                   SPIR-V 1.4, or WIP WebGPU validation rules.
+  --target-env                     {%s}
+                                   Use validation rules from the specified environment.
 )",
-      argv0, argv0);
+      argv0, argv0, target_env_list.c_str());
 }
 
 int main(int argc, char** argv) {