1 // Copyright (c) 2023 Google LLC. 2 // 3 // Licensed under the Apache License, Version 2.0 (the "License"); 4 // you may not use this file except in compliance with the License. 5 // You may obtain a copy of the License at 6 // 7 // http://www.apache.org/licenses/LICENSE-2.0 8 // 9 // Unless required by applicable law or agreed to in writing, software 10 // distributed under the License is distributed on an "AS IS" BASIS, 11 // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12 // See the License for the specific language governing permissions and 13 // limitations under the License. 14 15 #ifndef INCLUDE_SPIRV_TOOLS_UTIL_FLAGS_HPP_ 16 #define INCLUDE_SPIRV_TOOLS_UTIL_FLAGS_HPP_ 17 18 #include <stdint.h> 19 20 #include <functional> 21 #include <string> 22 #include <variant> 23 #include <vector> 24 25 // This file provides some utils to define a command-line interface with 26 // required and optional flags. 27 // - Flag order is not checked. 28 // - Currently supported flag types: BOOLEAN, STRING 29 // - As with most nix tools, using '--' in the command-line means all following 30 // tokens will be considered positional 31 // arguments. 32 // Example: binary -g -- -g --some-other-flag 33 // - the first `-g` is a flag. 34 // - the second `-g` is not a flag. 35 // - `--some-other-flag` is not a flag. 36 // - Both long-form and short-form flags are supported, but boolean flags don't 37 // support split boolean literals (short and long form). 38 // Example: 39 // -g : allowed, sets g to true. 40 // --my-flag : allowed, sets --my-flag to true. 41 // --my-flag=true : allowed, sets --my-flag to true. 42 // --my-flag true : NOT allowed. 43 // -g true : NOT allowed. 44 // --my-flag=TRUE : NOT allowed. 45 // 46 // - This implementation also supports string flags: 47 // -o myfile.spv : allowed, sets -o to `myfile.spv`. 48 // --output=myfile.spv : allowed, sets --output to `myfile.spv`. 49 // --output myfile.spv : allowd, sets --output to `myfile.spv`. 50 // 51 // Note: then second token is NOT checked for hyphens. 52 // --output -file.spv 53 // flag name: `output` 54 // flag value: `-file.spv` 55 // 56 // - This implementation generates flag at compile time. Meaning flag names 57 // must be valid C++ identifiers. 58 // However, flags are usually using hyphens for word separation. Hence 59 // renaming is done behind the scenes. Example: 60 // // Declaring a long-form flag. 61 // FLAG_LONG_bool(my_flag, [...]) 62 // 63 // -> in the code: flags::my_flag.value() 64 // -> command-line: --my-flag 65 // 66 // - The only additional lexing done is around '='. Otherwise token list is 67 // processed as received in the Parse() 68 // function. 69 // Lexing the '=' sign: 70 // - This is only done when parsing a long-form flag name. 71 // - the first '=' found is considered a marker for long-form, splitting 72 // the token into 2. 73 // Example: --option=value=abc -> [--option, value=abc] 74 // 75 // In most cases, you want to define some flags, parse them, and query them. 76 // Here is a small code sample: 77 // 78 // ```c 79 // // Defines a '-h' boolean flag for help printing, optional. 80 // FLAG_SHORT_bool(h, /*default=*/ false, "Print the help.", false); 81 // // Defines a '--my-flag' string flag, required. 82 // FLAG_LONG_string(my_flag, /*default=*/ "", "A magic flag!", true); 83 // 84 // int main(int argc, const char** argv) { 85 // if (!flags::Parse(argv)) { 86 // return -1; 87 // } 88 // 89 // if (flags::h.value()) { 90 // printf("usage: my-bin --my-flag=<value>\n"); 91 // return 0; 92 // } 93 // 94 // printf("flag value: %s\n", flags::my_flag.value().c_str()); 95 // for (const std::string& arg : flags::positional_arguments) { 96 // printf("arg: %s\n", arg.c_str()); 97 // } 98 // return 0; 99 // } 100 // ```c 101 102 // Those macros can be used to define flags. 103 // - They should be used in the global scope. 104 // - Underscores in the flag variable name are replaced with hyphens ('-'). 105 // 106 // Example: 107 // FLAG_SHORT_bool(my_flag, false, "some help", false); 108 // - in the code: flags::my_flag 109 // - command line: --my-flag=true 110 // 111 #define FLAG_LONG_string(Name, Default, Required) \ 112 UTIL_FLAGS_FLAG_LONG(std::string, Name, Default, Required) 113 #define FLAG_LONG_bool(Name, Default, Required) \ 114 UTIL_FLAGS_FLAG_LONG(bool, Name, Default, Required) 115 #define FLAG_LONG_uint(Name, Default, Required) \ 116 UTIL_FLAGS_FLAG_LONG(uint32_t, Name, Default, Required) 117 118 #define FLAG_SHORT_string(Name, Default, Required) \ 119 UTIL_FLAGS_FLAG_SHORT(std::string, Name, Default, Required) 120 #define FLAG_SHORT_bool(Name, Default, Required) \ 121 UTIL_FLAGS_FLAG_SHORT(bool, Name, Default, Required) 122 #define FLAG_SHORT_uint(Name, Default, Required) \ 123 UTIL_FLAGS_FLAG_SHORT(uint32_t, Name, Default, Required) 124 125 namespace flags { 126 127 // Parse the command-line arguments, checking flags, and separating positional 128 // arguments from flags. 129 // 130 // * argv: the argv array received in the main function. This utility expects 131 // the last pointer to 132 // be NULL, as it should if coming from the main() function. 133 // 134 // Returns `true` if the parsing succeeds, `false` otherwise. 135 bool Parse(const char** argv); 136 137 } // namespace flags 138 139 // ===================== BEGIN NON-PUBLIC SECTION ============================= 140 // All the code below belongs to the implementation, and there is no guaranteed 141 // around the API stability. Please do not use it directly. 142 143 // Defines the static variable holding the flag, allowing access like 144 // flags::my_flag. 145 // By creating the FlagRegistration object, the flag can be added to 146 // the global list. 147 // The final `extern` definition is ONLY useful for clang-format: 148 // - if the macro doesn't ends with a semicolon, clang-format goes wild. 149 // - cannot disable clang-format for those macros on clang < 16. 150 // (https://github.com/llvm/llvm-project/issues/54522) 151 // - cannot allow trailing semi (-Wextra-semi). 152 #define UTIL_FLAGS_FLAG(Type, Prefix, Name, Default, Required, IsShort) \ 153 namespace flags { \ 154 Flag<Type> Name(Default); \ 155 namespace { \ 156 static FlagRegistration Name##_registration(Name, Prefix #Name, Required, \ 157 IsShort); \ 158 } \ 159 } \ 160 extern flags::Flag<Type> flags::Name 161 162 #define UTIL_FLAGS_FLAG_LONG(Type, Name, Default, Required) \ 163 UTIL_FLAGS_FLAG(Type, "--", Name, Default, Required, false) 164 #define UTIL_FLAGS_FLAG_SHORT(Type, Name, Default, Required) \ 165 UTIL_FLAGS_FLAG(Type, "-", Name, Default, Required, true) 166 167 namespace flags { 168 169 // Just a wrapper around the flag value. 170 template <typename T> 171 struct Flag { 172 public: FlagFlag173 Flag(T&& default_value) : value_(default_value) {} 174 Flag(Flag&& other) = delete; 175 Flag(const Flag& other) = delete; 176 valueFlag177 const T& value() const { return value_; } valueFlag178 T& value() { return value_; } 179 180 private: 181 T value_; 182 }; 183 184 // To add support for new flag-types, this needs to be extended, and the visitor 185 // below. 186 using FlagType = std::variant<std::reference_wrapper<Flag<std::string>>, 187 std::reference_wrapper<Flag<bool>>, 188 std::reference_wrapper<Flag<uint32_t>>>; 189 190 template <class> 191 inline constexpr bool always_false_v = false; 192 193 extern std::vector<std::string> positional_arguments; 194 195 // Static class keeping track of the flags/arguments values. 196 class FlagList { 197 struct FlagInfo { FlagInfoFlagInfo198 FlagInfo(FlagType&& flag_, std::string&& name_, bool required_, 199 bool is_short_) 200 : flag(std::move(flag_)), 201 name(std::move(name_)), 202 required(required_), 203 is_short(is_short_) {} 204 205 FlagType flag; 206 std::string name; 207 bool required; 208 bool is_short; 209 }; 210 211 public: 212 template <typename T> register_flag(Flag<T> & flag,std::string && name,bool required,bool is_short)213 static void register_flag(Flag<T>& flag, std::string&& name, bool required, 214 bool is_short) { 215 get_flags().emplace_back(flag, std::move(name), required, is_short); 216 } 217 218 static bool parse(const char** argv); 219 220 #ifdef TESTING 221 // Flags are supposed to be constant for the whole app execution, hence the 222 // static storage. Gtest doesn't fork before running a test, meaning we have 223 // to manually clear the context at teardown. reset()224 static void reset() { 225 get_flags().clear(); 226 positional_arguments.clear(); 227 } 228 #endif 229 230 private: get_flags()231 static std::vector<FlagInfo>& get_flags() { 232 static std::vector<FlagInfo> flags; 233 return flags; 234 } 235 236 static bool parse_flag_info(FlagInfo& info, const char*** iterator); 237 static void print_usage(const char* binary_name, 238 const std::string& usage_format); 239 }; 240 241 template <typename T> 242 struct FlagRegistration { FlagRegistrationFlagRegistration243 FlagRegistration(Flag<T>& flag, std::string&& name, bool required, 244 bool is_short) { 245 std::string fixed_name = name; 246 for (auto& c : fixed_name) { 247 if (c == '_') { 248 c = '-'; 249 } 250 } 251 252 FlagList::register_flag(flag, std::move(fixed_name), required, is_short); 253 } 254 }; 255 256 // Explicit deduction guide to avoid `-Wctad-maybe-unsupported`. 257 template <typename T> 258 FlagRegistration(Flag<T>&, std::string&&, bool, bool) -> FlagRegistration<T>; 259 260 } // namespace flags 261 262 #endif // INCLUDE_SPIRV_TOOLS_UTIL_FLAGS_HPP_ 263