0.9.8.10
 All Classes Namespaces Files Functions Variables Typedefs Enumerations Enumerator Friends Macros Modules Pages
Compiler.cc
Go to the documentation of this file.
1 /*
2  * Copyright (C) 2007-2015 Hypertable, Inc.
3  *
4  * This file is part of Hypertable.
5  *
6  * Hypertable is free software; you can redistribute it and/or
7  * modify it under the terms of the GNU General Public License
8  * as published by the Free Software Foundation; version 3 of the
9  * License, or any later version.
10  *
11  * Hypertable is distributed in the hope that it will be useful,
12  * but WITHOUT ANY WARRANTY; without even the implied warranty of
13  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
14  * GNU General Public License for more details.
15  *
16  * You should have received a copy of the GNU General Public License
17  * along with this program; if not, write to the Free Software
18  * Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA
19  * 02110-1301, USA.
20  */
21 
26 
27 #include <Common/Compat.h>
28 
29 #include "Compiler.h"
30 #include "Tokenizer.h"
31 #include "TokenizerTools.h"
32 
33 #include <Common/FileUtils.h>
34 #include <Common/Logger.h>
35 #include <Common/String.h>
36 #include <Common/System.h>
37 #include <Common/Version.h>
38 
39 #include <boost/algorithm/string.hpp>
40 
41 #include <cctype>
42 #include <cerrno>
43 #include <cstdlib>
44 #include <ctime>
45 #include <fstream>
46 #include <iostream>
47 #include<map>
48 #include <stack>
49 #include <string>
50 #include <vector>
51 
52 extern "C" {
53 #include <pwd.h>
54 #include <sys/stat.h>
55 #include <sys/types.h>
56 #include <unistd.h>
57 }
58 
59 #if defined(__APPLE__)
60 extern char **environ;
61 #endif
62 
63 #define TASK_COLUMN_WIDTH 28
64 #define DESCRIPTION_COLUMN_WIDTH (79-TASK_COLUMN_WIDTH)
65 
66 using namespace Hypertable;
67 using namespace Hypertable::ClusterDefinitionFile;
68 using namespace std;
69 
70 namespace {
71 
72  string extract_short_description(const string &description) {
73  string short_description;
74  const char *ptr;
75  for (ptr = description.c_str(); *ptr; ptr++) {
76  if (*ptr == '.' && (*(ptr+1) == 0 || isspace(*(ptr+1))))
77  break;
78  }
79  if ((ptr - description.c_str()) <= DESCRIPTION_COLUMN_WIDTH)
80  short_description = description.substr(0, ptr - description.c_str());
81  else
82  short_description = description.substr(0, DESCRIPTION_COLUMN_WIDTH);
83  return short_description;
84  }
85 
86  void extract_long_description(const string &description, vector<string> &lines) {
87 
88  string long_description;
89  lines.clear();
90  const char *base = description.c_str();
91  const char *end = base + description.length();
92  const char *ptr;
93  while (base < end) {
94  // skip whitespace
95  while (*base && isspace(*base))
96  base++;
97 
98  if ((end-base) > 79) {
99  ptr = base + 79;
100  for (size_t i=0; i<20; i++) {
101  if (isspace(*ptr)) {
102  lines.push_back(string(base, ptr-base));
103  base = ptr;
104  break;
105  }
106  ptr--;
107  }
108  if (ptr != base) {
109  ptr = base + 79;
110  while (*ptr && !isspace(*ptr))
111  ptr++;
112  lines.push_back(string(base, ptr-base));
113  base = ptr;
114  }
115  }
116  else {
117  lines.push_back(base);
118  break;
119  }
120  }
121  return;
122  }
123 
124  const string construct_with_function(set<string> &roles) {
125  string text = "with () {\n";
126  text.append(" local args=(\"$@\")\n");
127  text.append(" if [ ${#args[@]} != 2 ]; then\n");
128  text.append(" exit 0\n");
129  text.append(" fi\n");
130  if (!roles.empty()) {
131  text.append(" IFS=',' read -ra ROLES <<< \"${args[0]}\"\n");
132  text.append(" ON=\n");
133  text.append(" for role in \"${ROLES[@]}\"; do\n");
134  text.append(" if [ $role == \"all\" ]; then\n");
135  text.append(" ON=\"");
136  for (auto & role : roles)
137  text.append(format("(${ROLE_%s}) ", role.c_str()));
138  text.append("\"\n");
139  for (auto & role : roles) {
140  text.append(format(" elif [ $role == \"%s\" ]; then\n", role.c_str()));
141  text.append(format(" ON=\"$ON (${ROLE_%s})\"\n", role.c_str()));
142  }
143  text.append(" fi\n");
144  text.append(" done\n");
145  text.append(format(" %s/bin/ht ssh \" $ON\" \"${args[1]}\"\n",
146  System::install_dir.c_str()));
147  }
148  text.append("}\n");
149  return text;
150  }
151 
152 }
153 
154 Compiler::Compiler(const string &fname) : m_definition_file(fname) {
155  struct passwd *pw = getpwuid(getuid());
156  m_output_script.append(pw->pw_dir);
157  m_output_script.append("/.cluster");
159  m_output_script.append(".sh");
160 
161  if (compilation_needed())
162  make();
163 }
164 
165 
167 
169  return true;
170 
171  struct stat statbuf;
172 
173  if (stat(m_definition_file.c_str(), &statbuf) < 0) {
174  cout << "stat('" << m_definition_file << "') - " << strerror(errno) << endl;
175  exit(1);
176  }
177  time_t definition_modification_time = statbuf.st_mtime;
178 
179  if (stat(m_output_script.c_str(), &statbuf) < 0) {
180  cout << "stat('" << m_output_script << "') - " << strerror(errno) << endl;
181  exit(1);
182  }
183  time_t script_modification_time = statbuf.st_mtime;
184 
185  string line;
186  ifstream output_script_file(m_output_script);
187  const char *base;
188  const char *ptr;
189 
190  if (!output_script_file.is_open())
191  return true;
192 
193  while (getline(output_script_file, line)) {
194 
195  if (line.empty() || line[0] != '#')
196  break;
197 
198  base = line.c_str() + 1;
199  ptr = strchr(base, ':');
200  if (ptr != 0) {
201  string tag(base, ptr-base);
202  boost::trim(tag);
203  if (tag.compare("version") == 0) {
204  string value(ptr+1);
205  boost::trim(value);
206  if (strcmp(value.c_str(), version_string()))
207  return true;
208  }
209  else if (tag.compare("dependency") == 0) {
210  string dependency_file(ptr+1);
211  boost::trim(dependency_file);
212  if (stat(dependency_file.c_str(), &statbuf) < 0)
213  return true;
214  if (statbuf.st_mtime > definition_modification_time)
215  definition_modification_time = statbuf.st_mtime;
216  }
217  }
218  }
219 
220  return definition_modification_time > script_modification_time;
221 
222 }
223 
225  size_t lastslash = m_output_script.find_last_of('/');
226  HT_ASSERT(lastslash != string::npos);
227 
228  string script_directory = m_output_script.substr(0, lastslash);
229  if (!FileUtils::mkdirs(script_directory)) {
230  cout << "mkdirs('" << script_directory << "') - " << strerror(errno) << endl;
231  exit(1);
232  }
233 
234  // Create map of environment variables
235  map<string, string> environ_map;
236  for (size_t i=0; environ[i]; i++) {
237  const char *ptr = strchr(environ[i], '=');
238  HT_ASSERT(ptr);
239  environ_map[string(environ[i], ptr-environ[i])] = string(ptr+1);
240  }
241 
242  stack<TokenizerPtr> definitions;
243 
244  definitions.push( make_shared<Tokenizer>(m_definition_file) );
245 
246  string header;
247  string output;
248  Token token;
249  TranslationContext context;
250  bool first;
251 
252  header.append("#!/bin/bash\n");
253  header.append("#\n");
254  header.append(Hypertable::format("# version: %s\n", version_string()));
255 
256  while (!definitions.empty()) {
257  while (definitions.top()->next(token)) {
258  if (token.translator)
259  output.append(token.translator->translate(context));
260  if (token.type == Token::INCLUDE) {
261  string include_file = token.text.substr(token.text.find_first_of("include:")+8);
262  boost::trim_if(include_file, boost::is_any_of("'\" \t\n\r"));
263  while (TokenizerTools::substitute_variables(include_file, include_file, context.symbols))
264  ;
265  TokenizerTools::substitute_variables(include_file, include_file, environ_map);
266  if (include_file[0] != '/')
267  include_file = definitions.top()->dirname() + "/" + include_file;
268  definitions.push( make_shared<Tokenizer>(include_file) );
269  header.append(Hypertable::format("# dependency: %s\n", include_file.c_str()));
270  }
271  }
272  definitions.pop();
273  }
274  header.append("\n");
275 
276  // CLUSTER_BUILTIN_display_line function
277  output.append("\n");
278  output.append("CLUSTER_BUILTIN_display_line () {\n");
279  output.append(" let size=$1\n");
280  output.append(" for ((i=0; i<$size; i++)); do\n");
281  output.append(" echo -n \"=\"\n");
282  output.append(" done\n");
283  output.append(" echo\n");
284  output.append("}\n");
285 
286  // show_variables function
287  output.append("\n");
288  output.append("show_variables () {\n");
289  output.append(" echo\n");
290  for (auto & entry : context.symbols) {
291  output.append(" echo \"");
292  output.append(entry.first);
293  output.append("=${");
294  output.append(entry.first);
295  output.append("}\"\n");
296  }
297  output.append(" echo\n");
298  output.append("}\n");
299 
300  // with function
301  output.append(construct_with_function(context.roles));
302 
303  const char *dots = "....................................................";
304  output.append("\n");
305  output.append("if [ $1 == \"-T\" ] || [ $1 == \"--tasks\" ]; then\n");
306  output.append(" echo\n");
307  output.append(" echo \"TASK DESCRIPTION\"\n");
308  output.append(" echo \"============================= =================================================\"\n");
309  for (auto & entry : context.tasks) {
310  output.append(" echo \"");
311  output.append(entry.first);
312  output.append(" ");
313  if (entry.first.length() <= TASK_COLUMN_WIDTH)
314  output.append(dots, TASK_COLUMN_WIDTH-entry.first.length());
315  else
316  output.append("..");
317  output.append(" ");
318  output.append(extract_short_description(entry.second));
319  output.append("\"\n");
320  }
321  output.append(" echo\n");
322  output.append(" exit 0\n");
323  output.append("elif [ $1 == \"-e\" ] || [ $1 == \"--explain\" ]; then\n");
324  output.append(" shift\n");
325  output.append(" if [ $# -eq 0 ]; then\n");
326  output.append(" echo \"Missing task name in -e option\"\n");
327  output.append(" exit 1\n");
328  output.append(" fi\n");
329 
330  if (!context.tasks.empty()) {
331  vector<string> lines;
332  first = true;
333  for (auto & entry : context.tasks) {
334  if (first) {
335  output.append(" if [ $1 == \"");
336  first = false;
337  }
338  else
339  output.append(" elif [ $1 == \"");
340  output.append(entry.first);
341  output.append("\" ]; then\n");
342  output.append(" echo\n");
343  output.append(" echo \"$1\"\n");
344  output.append(" CLUSTER_BUILTIN_display_line ${#1}\n");
345  extract_long_description(entry.second, lines);
346  for (auto & line : lines) {
347  output.append(" echo \"");
348  output.append(line);
349  output.append("\"\n");
350  }
351  if (context.task_roles[entry.first].empty())
352  output.append(" echo \"-- ROLES: all\"\n");
353  else {
354  output.append(" echo \"-- ROLES: ");
355  output.append(context.task_roles[entry.first]);
356  output.append("\"\n");
357  }
358  }
359  output.append(" else\n");
360  output.append(" echo \"Task '$1' is not defined.\"\n");
361  output.append(" exit 1\n");
362  output.append(" fi\n");
363  output.append(" echo\n");
364  output.append(" exit 0\n");
365  }
366  else {
367  output.append(" echo \"Task '$1' is not defined.\"\n");
368  output.append(" exit 1\n");
369  }
370 
371  output.append("fi\n");
372 
373  // Generate task targets
374  if (!context.tasks.empty()) {
375  first = true;
376  for (auto & entry : context.tasks) {
377  if (first) {
378  output.append(format("if [ $1 == \"%s\" ]; then\n shift\n %s $@\n",
379  entry.first.c_str(), entry.first.c_str()));
380  first = false;
381  }
382  else
383  output.append(format("elif [ $1 == \"%s\" ]; then\n shift\n %s $@\n",
384  entry.first.c_str(), entry.first.c_str()));
385  }
386  output.append("elif [ $1 == \"show_variables\" ]; then\n show_variables\n");
387  output.append("elif [ $1 == \"with\" ]; then\n shift\n with \"$@\"\n");
388  output.append("else\n");
389  output.append(" echo \"Task '$1' is not defined.\"\n");
390  output.append(" exit 1\n");
391  output.append("fi\n");
392  }
393  else {
394  output.append("if [ $1 == \"show_variables\" ]; then\n show_variables\n");
395  output.append("elif [ $1 == \"with\" ]; then\n shift\n with \"$@\"\n");
396  output.append("else\n");
397  output.append(" echo \"Task '$1' is not defined.\"\n");
398  output.append(" exit 1\n");
399  output.append("fi\n");
400  }
401 
402  if (FileUtils::write(m_output_script, header + output) < 0)
403  exit(1);
404 
405  if (chmod(m_output_script.c_str(), 0755) < 0) {
406  cout << "chmod('" << m_output_script << "', 0755) failed - "
407  << strerror(errno) << endl;
408  exit(1);
409  }
410 
411 }
bool compilation_needed()
Determines if script needs to be re-built.
Definition: Compiler.cc:166
Retrieves system information (hardware, installation directory, etc)
TranslatorPtr translator
Translator object for token text.
Definition: Token.h:75
bool substitute_variables(const string &input, string &output, map< string, string > &vmap)
Does variable sustitution in a block of text.
String format(const char *fmt,...)
Returns a String using printf like format facilities Vanilla snprintf is about 1.5x faster than this...
Definition: String.cc:37
static ssize_t write(const String &fname, const std::string &contents)
Writes a String buffer to a file; the file is overwritten if it already exists.
Definition: FileUtils.cc:124
string m_definition_file
Cluster definition file.
Definition: Compiler.h:138
static bool exists(const String &fname)
Checks if a file or directory exists.
Definition: FileUtils.cc:420
STL namespace.
static bool mkdirs(const String &dirname)
Creates a directory (with all parent directories, if required)
Definition: FileUtils.cc:366
Cluster definition file token.
Definition: Token.h:43
#define HT_ASSERT(_e_)
Definition: Logger.h:396
#define TASK_COLUMN_WIDTH
Definition: Compiler.cc:63
File system utility functions.
void make()
Compiles the definition file into a task execution script.
Definition: Compiler.cc:224
Logging routines and macros.
Compatibility Macros for C/C++.
Hypertable definitions
Declarations for Compiler.
map< string, string > symbols
Map of variable names to default values.
static String install_dir
The installation directory.
Definition: System.h:114
Declarations for TokenizerTools.
A String class based on std::string.
Compiler(const string &fname)
Constructor.
Definition: Compiler.cc:154
Context used to translate cluster definition statements.
Declarations for Tokenizer.
Cluster definition file translation definitions.
Definition: Compiler.h:35
#define DESCRIPTION_COLUMN_WIDTH
Definition: Compiler.cc:64
const char * version_string()
Definition: Version.cc:37
map< string, string > tasks
Map of tasks to descriptive text.