1 /**
2 Copyright: Copyright (c) 2017, Joakim Brännström. All rights reserved.
3 License: MPL-2
4 Author: Joakim Brännström (joakim.brannstrom@gmx.com)
5 
6 This Source Code Form is subject to the terms of the Mozilla Public License,
7 v.2.0. If a copy of the MPL was not distributed with this file, You can obtain
8 one at http://mozilla.org/MPL/2.0/.
9 */
10 module dextool.plugin.intercept.frontend.raw_args;
11 
12 import std.typecons : Nullable;
13 
14 import logger = std.experimental.logger;
15 
16 import dextool.compilation_db;
17 import dextool.type;
18 import dextool.utility;
19 
20 import dextool.plugin.types;
21 
22 import dextool.plugin.intercept.type;
23 
24 struct RawConfiguration {
25     import std.getopt : getopt, GetoptResult;
26 
27     Nullable!XmlConfig xmlConfig;
28 
29     string[] testDoubleInclude;
30     string[] inFiles;
31     string[] cflags;
32     string[] compileDb;
33     string header;
34     string headerFile;
35     string mainFileName = "intercept";
36     string prefix = "intercept_";
37     string stripInclude;
38     string out_;
39     string config;
40     bool help;
41     bool shortPluginHelp;
42 
43     private GetoptResult help_info;
44 
45     void parse(string[] args) {
46         static import std.getopt;
47 
48         try {
49             // dfmt off
50             help_info = getopt(args, std.getopt.config.keepEndOfOptions,
51                    "short-plugin-help", "short description of the plugin",  &shortPluginHelp,
52                    "main-fname", "Used as part of filename for generated files [default: intercept]", &mainFileName,
53                    "out", "directory for generated files [default: ./]", &out_,
54                    "compile-db", "Retrieve compilation parameters from the file", &compileDb,
55                    "prefix", "Prefix all function calls to the intercepted target [default: intercept_]", &prefix,
56                    "strip-incl", "A regex used to strip the include paths", &stripInclude,
57                    "header", "Prepend generated files with the string", &header,
58                    "header-file", "Prepend generated files with the header read from the file", &headerFile,
59                    "td-include", "User supplied includes used instead of those found", &testDoubleInclude,
60                    "in", "Input file to parse (at least one)", &inFiles,
61                    "config", "Use configuration file", &config);
62             // dfmt on
63             help = help_info.helpWanted;
64         }
65         catch (std.getopt.GetOptException ex) {
66             logger.error(ex.msg);
67             help = true;
68         }
69 
70         // default arguments
71         if (stripInclude.length == 0) {
72             stripInclude = r".*/(.*)";
73             logger.trace("--strip-incl: using default regex to strip include path (basename)");
74         }
75 
76         if (config.length != 0) {
77             xmlConfig = readRawConfig(FileName(config));
78             if (xmlConfig.isNull) {
79                 help = true;
80             }
81         }
82 
83         import std.algorithm : find;
84         import std.array : array;
85         import std.range : drop;
86 
87         // at this point args contain "what is left". What is interesting then is those after "--".
88         cflags = args.find("--").drop(1).array();
89     }
90 
91     void printHelp() {
92         import std.getopt : defaultGetoptPrinter;
93         import std.stdio : writeln;
94 
95         defaultGetoptPrinter("Usage: dextool intercept [options] [--in=] [-- CFLAGS...]",
96                 help_info.options);
97 
98         writeln("
99 REGEX
100 The regex syntax is found at http://dlang.org/phobos/std_regex.html
101 
102 Information about --strip-incl.
103   Default regexp is: .*/(.*)
104 
105   To allow the user to selectively extract parts of the include path dextool
106   applies the regex and then concatenates all the matcher groups found.  It is
107   turned into the replacement include path.
108 
109   Important to remember then is that this approach requires that at least one
110   matcher group exists.
111 ");
112     }
113 }
114 
115 /// Symbols to intercept.
116 @safe struct Symbols {
117     InterceptSymbol[SymbolName] syms;
118     alias syms this;
119 
120     bool contains(string symbol) {
121         if (SymbolName(symbol) in syms)
122             return true;
123         return false;
124     }
125 
126     bool hasSymbols() {
127         return syms.length != 0;
128     }
129 
130     void put(InterceptSymbol data) {
131         syms[data.funcName] = data;
132     }
133 }
134 
135 /// A symbol to intercept.
136 @safe struct InterceptSymbol {
137     SymbolName funcName;
138     string prefix;
139 }
140 
141 /** Extracted configuration data from an XML file.
142  *
143  * It is not inteded to be used as is but rather further processed.
144  */
145 struct XmlConfig {
146     import dextool.type : DextoolVersion, RawCliArguments, FilterClangFlag;
147 
148     DextoolVersion version_;
149     int skipCompilerArgs;
150     RawCliArguments command;
151     FilterClangFlag[] filterClangFlags;
152 
153     /// Symbols to intercept.
154     Symbols symbols;
155 }
156 
157 auto parseRawConfig(T)(T xml) @trusted {
158     import std.conv : to, ConvException;
159     import std.xml;
160     import dextool.utility : DextoolVersion;
161     import dextool.type : RawCliArguments, FilterClangFlag;
162 
163     DextoolVersion version_;
164     int skip_flags = 1;
165     RawCliArguments command;
166     FilterClangFlag[] filter_clang_flags;
167     Symbols syms;
168 
169     if (auto tag = "version" in xml.tag.attr) {
170         version_ = *tag;
171     }
172 
173     // dfmt off
174     xml.onStartTag["compiler_flag_filter"] = (ElementParser filter_flags) {
175         if (auto tag = "skip_compiler_args" in xml.tag.attr) {
176             try {
177                 skip_flags = (*tag).to!int;
178             }
179             catch (ConvException ex) {
180                 logger.info(ex.msg);
181                 logger.info("   using fallback '1'");
182             }
183         }
184 
185         xml.onEndTag["exclude"] = (const Element e) { filter_clang_flags ~= FilterClangFlag(e.text()); };
186     };
187     xml.onStartTag["intercept"] = (ElementParser filter_sym) {
188         xml.onEndTag["func"] = (const Element e) {
189             if (auto pref = "prefix" in e.tag.attr) {
190                 auto sym = InterceptSymbol(SymbolName(e.text), *pref);
191                 syms.put(sym);
192             } else {
193                 logger.warningf("xml-config: missing the attribute prefix for intercept func '%s'", e.text);
194             }
195         };
196     };
197     // dfmt on
198     xml.parse();
199 
200     return XmlConfig(version_, skip_flags, command, filter_clang_flags, syms);
201 }
202 
203 static import dextool.xml;
204 
205 alias readRawConfig = dextool.xml.readRawConfig!(XmlConfig, parseRawConfig);