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