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