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