xref: /third_party/spirv-tools/tools/util/flags.h (revision fd4e5da5)
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
125namespace 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.
135bool 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
167namespace flags {
168
169// Just a wrapper around the flag value.
170template <typename T>
171struct Flag {
172 public:
173  Flag(T&& default_value) : value_(default_value) {}
174  Flag(Flag&& other) = delete;
175  Flag(const Flag& other) = delete;
176
177  const T& value() const { return value_; }
178  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.
186using FlagType = std::variant<std::reference_wrapper<Flag<std::string>>,
187                              std::reference_wrapper<Flag<bool>>,
188                              std::reference_wrapper<Flag<uint32_t>>>;
189
190template <class>
191inline constexpr bool always_false_v = false;
192
193extern std::vector<std::string> positional_arguments;
194
195// Static class keeping track of the flags/arguments values.
196class FlagList {
197  struct FlagInfo {
198    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>
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.
224  static void reset() {
225    get_flags().clear();
226    positional_arguments.clear();
227  }
228#endif
229
230 private:
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
241template <typename T>
242struct FlagRegistration {
243  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`.
257template <typename T>
258FlagRegistration(Flag<T>&, std::string&&, bool, bool) -> FlagRegistration<T>;
259
260}  // namespace flags
261
262#endif  // INCLUDE_SPIRV_TOOLS_UTIL_FLAGS_HPP_
263