]> git.tdb.fi Git - builder.git/blob - source/config.cpp
Exception handling fixes
[builder.git] / source / config.cpp
1 /* $Id$
2
3 This file is part of builder
4 Copyright © 2006-2007 Mikko Rasa, Mikkosoft Productions
5 Distributed under the LGPL
6 */
7
8 #include <msp/core/except.h>
9 #include <msp/io/except.h>
10 #include <msp/io/file.h>
11 #include <msp/io/print.h>
12 #include <msp/path/utils.h>
13 #include <msp/time/utils.h>
14 #include "builder.h"
15 #include "config.h"
16 #include "sourcepackage.h"
17
18 using namespace std;
19 using namespace Msp;
20
21 Config::Config(SourcePackage &p):
22         package(p),
23         freeze_mtime(false)
24 { }
25
26 /**
27 Adds a configuration option.
28
29 @param   n  Option name
30 @param   v  Default value
31 @param   d  Description
32 */
33 void Config::add_option(const string &n, const string &v, const string &d)
34 {
35         options.insert(OptionMap::value_type(n, Option(n, v, d)));
36 }
37
38 /**
39 Gets the given option from the configuration.  If the option doesn't exist,
40 an Exception is thrown.
41 */
42 const Config::Option &Config::get_option(const string &name) const
43 {
44         OptionMap::const_iterator i=options.find(name);
45         if(i==options.end())
46                 throw Exception("Tried to access nonexistent option "+name);
47
48         return i->second;
49 }
50
51 /**
52 Checks whether an option with the given name exists.
53 */
54 bool Config::is_option(const string &name) const
55 {
56         return options.count(name);
57 }
58
59 /**
60 Selects the last profile used.  If the profile cache file is not present, the
61 default profile is assumed.
62 */
63 void Config::select_last_profile()
64 {
65         try
66         {
67                 IO::File in((package.get_source()/".profile.cache").str());
68                 string profile;
69                 in.getline(profile);
70                 set_option("profile", profile);
71         }
72         catch(const IO::FileNotFound &)
73         { }
74
75         freeze_mtime=true;
76         package.get_builder().apply_profile_template(*this, get_option("profile").value);
77         freeze_mtime=false;
78
79         load();
80 }
81
82 /**
83 Selects the given profile.  The profile cache file is updated as well, unless
84 --dry-run was given.
85 */
86 void Config::select_profile(const string &profile)
87 {
88         set_option("profile", profile);
89
90         if(!package.get_builder().get_dry_run())
91         {
92                 IO::File out((package.get_source()/".profile.cache").str(), IO::M_WRITE);
93                 IO::print(out, "%s\n", profile);
94         }
95
96         freeze_mtime=true;
97         package.get_builder().apply_profile_template(*this, profile);
98         freeze_mtime=false;
99
100         load();
101 }
102
103 /**
104 Processes options from the given raw option map.  Nonexistent options are
105 ignored.  If any options were changed, the mtime of the configuration is updated
106 to the current time.
107
108 @param   opts  A map to process options from
109
110 @return  Whether any option values were changed
111 */
112 bool Config::update(const StringMap &opts)
113 {
114         bool changed=false;
115         for(StringMap::const_iterator i=opts.begin(); i!=opts.end(); ++i)
116         {
117                 if(set_option(i->first, i->second) && i->first!="profile")
118                         changed=true;
119         }
120
121         if(changed && !freeze_mtime)
122                 mtime=Time::now();
123
124         return changed;
125 }
126
127 /**
128 Expands any variable references in options.
129 */
130 void Config::finish()
131 {
132         for(unsigned n=0; n<20; ++n)
133         {
134                 bool changed=false;
135                 for(OptionMap::iterator i=options.begin(); i!=options.end(); ++i)
136                 {
137                         Option &opt=i->second;
138                         unsigned dollar=0;
139                         while((dollar=opt.value.find('$', dollar))!=string::npos)
140                         {
141                                 unsigned end;
142                                 string var;
143                                 if(opt.value[dollar+1]=='{')
144                                 {
145                                         end=opt.value.find('}', dollar+2);
146                                         if(end==string::npos)
147                                                 throw Exception("Unterminated variable reference");
148                                         var=opt.value.substr(dollar+2, end-dollar-2);
149                                         ++end;
150                                 }
151                                 else
152                                 {
153                                         for(end=dollar+1; (isalnum(opt.value[end]) && opt.value[end]!='_'); ++end);
154                                         var=opt.value.substr(dollar+1, end-dollar-1);
155                                 }
156
157                                 string value;
158                                 if(is_option(var))
159                                         value=get_option(var).value;
160                                 else
161                                         value=getenv(var.c_str());
162
163                                 opt.value.replace(dollar, end-dollar, value);
164
165                                 dollar+=value.size();
166                                 changed=true;
167                         }
168                 }
169
170                 if(!changed)
171                         break;
172         }
173 }
174
175 void Config::save() const
176 {
177         Path fn=package.get_source()/".options.cache";
178
179         OptionMap::const_iterator i=options.find("profile");
180         if(i!=options.end())
181                 fn=package.get_source()/(".options."+i->second.value+".cache");
182
183         IO::File out(fn.str(), IO::M_WRITE);
184
185         for(i=options.begin(); i!=options.end(); ++i)
186                 IO::print(out, "option \"%s\" \"%s\";\n", i->second.name, i->second.value);
187 }
188
189 bool Config::set_option(const string &opt, const string &val)
190 {
191         bool result=false;
192
193         OptionMap::iterator i=options.find(opt);
194         if(i!=options.end())
195         {
196                 if(i->second.value!=val)
197                         result=true;
198                 i->second.value=val;
199         }
200
201         return result;
202 }
203
204 void Config::load()
205 {
206         Path fn=package.get_source()/".options.cache";
207
208         OptionMap::iterator i=options.find("profile");
209         if(i!=options.end())
210                 fn=package.get_source()/(".options."+i->second.value+".cache");
211
212         try
213         {
214                 IO::File in(fn.str());
215
216                 mtime=Time::TimeStamp::from_unixtime(stat(fn).st_mtime);
217
218                 DataFile::Parser parser(in, fn.str());
219                 Loader loader(*this);
220                 loader.load(parser);
221         }
222         catch(const SystemError &)
223         { }
224 }
225
226
227 Config::Option::Option(const string &n, const string &v, const string &d):
228         name(n),
229         defv(v),
230         descr(d),
231         value(v)
232 { }
233
234
235 Config::Loader::Loader(Config &c):
236         conf(c)
237 {
238         add("option", &Loader::option);
239 }
240
241 void Config::Loader::option(const string &n, const string &v)
242 {
243         conf.set_option(n, v);
244 }