summaryrefslogtreecommitdiffabout
path: root/lib/component_factory.cc
authorMichael Krelin <hacker@klever.net>2005-03-30 15:50:28 (UTC)
committer Michael Krelin <hacker@klever.net>2005-03-30 15:50:28 (UTC)
commit3ddbfeafde93d1aab16a710498d86eef4e787406 (patch) (unidiff)
treed53ef21cf3b2bddfeb957c98344d0be8759ed555 /lib/component_factory.cc
parent642dc685bd0a3f1526e22827a4539aa0e06aeff7 (diff)
downloadsitecing-3ddbfeafde93d1aab16a710498d86eef4e787406.zip
sitecing-3ddbfeafde93d1aab16a710498d86eef4e787406.tar.gz
sitecing-3ddbfeafde93d1aab16a710498d86eef4e787406.tar.bz2
1. preprocessor doesn't touch unchanged files anymore
2. doublechedk on whether file is up to date when making 3. changed the way preprocessor targets depend on the timestamp file 4. a bugfix in strip_suffix/strip_prefix
Diffstat (limited to 'lib/component_factory.cc') (more/less context) (show whitespace changes)
-rw-r--r--lib/component_factory.cc19
1 files changed, 13 insertions, 6 deletions
diff --git a/lib/component_factory.cc b/lib/component_factory.cc
index f8666dc..b8f5a16 100644
--- a/lib/component_factory.cc
+++ b/lib/component_factory.cc
@@ -1,175 +1,180 @@
1#ifdef USE_PCH 1#ifdef USE_PCH
2 #include "pch.h" 2 #include "pch.h"
3#else 3#else
4 #include <sys/types.h> 4 #include <sys/types.h>
5 #include <sys/stat.h> 5 #include <sys/stat.h>
6 #include <unistd.h> 6 #include <unistd.h>
7 #include <sys/wait.h> 7 #include <sys/wait.h>
8 #include <fcntl.h> 8 #include <fcntl.h>
9 #include <iostream> 9 #include <iostream>
10 #include <fstream> 10 #include <fstream>
11 #include <stdexcept> 11 #include <stdexcept>
12 #include <vector> 12 #include <vector>
13 using namespace std; 13 using namespace std;
14 #include "sitecing/component_factory.h" 14 #include "sitecing/component_factory.h"
15 #include "sitecing/sitecing_util.h" 15 #include "sitecing/sitecing_util.h"
16 #include "sitecing/sitecing_parser.h" 16 #include "sitecing/sitecing_parser.h"
17 #include "sitecing/sitecing_exception.h" 17 #include "sitecing/sitecing_exception.h"
18#endif 18#endif
19 19
20namespace sitecing { 20namespace sitecing {
21 21
22 static const char *pp_targets[] = { ".cc", ".h", ".imports", ".classname", ".baseclassname", ".ancestors" }; 22 static const char *pp_targets[] = { ".cc", ".h", ".imports", ".classname", ".baseclassname", ".ancestors", ".pp_stamp" };
23 static const char *cc_targets[] = { ".o", ".d" }; 23 static const char *cc_targets[] = { ".o", ".d" };
24 24
25 component_factory::component_factory(configuration& c) 25 component_factory::component_factory(configuration& c)
26 : config(c), 26 : config(c),
27 root_source(normalize_path(c.root_source,strip_trailing_slash)+'/'), 27 root_source(normalize_path(c.root_source,strip_trailing_slash)+'/'),
28 root_intermediate(normalize_path(c.root_intermediate,strip_trailing_slash)+'/'), 28 root_intermediate(normalize_path(c.root_intermediate,strip_trailing_slash)+'/'),
29 root_so(normalize_path(c.root_so,strip_trailing_slash)+'/') { 29 root_so(normalize_path(c.root_so,strip_trailing_slash)+'/') {
30 } 30 }
31 31
32 void component_factory::get_dependencies(const string& dst,file_list_t& deps) { 32 void component_factory::get_dependencies(const string& dst,file_list_t& deps) {
33 deps.clear(); 33 deps.clear();
34 string dp = normalize_path(dst,strip_trailing_slash); 34 string dp = normalize_path(dst,strip_trailing_slash);
35 // source documents 35 // source documents
36 try { // XXX: or just compare it off? 36 try { // XXX: or just compare it off?
37 string noro = strip_prefix(dp,root_source); 37 string noro = strip_prefix(dp,root_source);
38 return; 38 return;
39 }catch(utility_no_affix& una) { 39 }catch(utility_no_affix& una) {
40 } 40 }
41 // .so binaries 41 // .so binaries
42 try { 42 try {
43 string noso = strip_suffix(dp,".so"); 43 string noso = strip_suffix(dp,".so");
44 string noro = strip_prefix(noso,root_so); 44 string noro = strip_prefix(noso,root_so);
45 deps.push_back(root_intermediate+noro+".o"); 45 deps.push_back(root_intermediate+noro+".o");
46 config_options *co_so_deps = config.lookup_config(noro,config_options::flag_so_deps); 46 config_options *co_so_deps = config.lookup_config(noro,config_options::flag_so_deps);
47 if(co_so_deps) { 47 if(co_so_deps) {
48 for(list<string>::const_iterator i=co_so_deps->so_deps.begin();i!=co_so_deps->so_deps.end();++i) 48 for(list<string>::const_iterator i=co_so_deps->so_deps.begin();i!=co_so_deps->so_deps.end();++i)
49 deps.push_back(*i); 49 deps.push_back(*i);
50 } 50 }
51 return; 51 return;
52 }catch(utility_no_prefix& unp) { 52 }catch(utility_no_prefix& unp) {
53 throw konforka::exception(CODEPOINT,"component is outside of component root"); 53 throw konforka::exception(CODEPOINT,"component is outside of component root");
54 }catch(utility_no_suffix& uns) { 54 }catch(utility_no_suffix& uns) {
55 } 55 }
56 // preprocessor targets 56 // preprocessor targets
57 for(int ppt=0;ppt<sizeof(pp_targets)/sizeof(*pp_targets);ppt++) { 57 for(int ppt=0;ppt<sizeof(pp_targets)/sizeof(*pp_targets);ppt++) {
58 try { 58 try {
59 string nos = strip_suffix(dp,pp_targets[ppt]); 59 string nos = strip_suffix(dp,pp_targets[ppt]);
60 string noro = strip_prefix(nos,root_intermediate); 60 string noro = strip_prefix(nos,root_intermediate);
61 deps.push_back(root_source+noro); 61 deps.push_back(root_source+noro);
62 ifstream imports((root_intermediate+noro+".imports").c_str(),ios::in); 62 ifstream imports((root_intermediate+noro+".imports").c_str(),ios::in);
63 if(imports.good()) { 63 if(imports.good()) {
64 string str; 64 string str;
65 while(!imports.eof()) { 65 while(!imports.eof()) {
66 imports >> str; 66 imports >> str;
67 if(!str.empty()) 67 if(!str.empty())
68 deps.push_back(root_intermediate+str+".classname"); 68 deps.push_back(root_intermediate+str+".classname");
69 } 69 }
70 } 70 }
71 ifstream ancestors((root_intermediate+noro+".ancestors").c_str(),ios::in); 71 ifstream ancestors((root_intermediate+noro+".ancestors").c_str(),ios::in);
72 if(ancestors.good()) { 72 if(ancestors.good()) {
73 string str; 73 string str;
74 while(!ancestors.eof()) { 74 while(!ancestors.eof()) {
75 ancestors >> str; 75 ancestors >> str;
76 if(!str.empty()) 76 if(!str.empty())
77 deps.push_back(root_intermediate+str+".classname"); 77 deps.push_back(root_intermediate+str+".classname");
78 } 78 }
79 } 79 }
80 config_options *co_intermediate_deps = config.lookup_config(noro,config_options::flag_intermediate_deps); 80 config_options *co_intermediate_deps = config.lookup_config(noro,config_options::flag_intermediate_deps);
81 if(co_intermediate_deps) { 81 if(co_intermediate_deps) {
82 for(list<string>::const_iterator i=co_intermediate_deps->intermediate_deps.begin();i!=co_intermediate_deps->intermediate_deps.end();++i) 82 for(list<string>::const_iterator i=co_intermediate_deps->intermediate_deps.begin();i!=co_intermediate_deps->intermediate_deps.end();++i)
83 deps.push_back(*i); 83 deps.push_back(*i);
84 } 84 }
85 return; 85 return;
86 }catch(utility_no_affix& una) { 86 }catch(utility_no_affix& una) {
87 // do nothing. must be a cpp dependency. 87 // do nothing. must be a cpp dependency.
88 } 88 }
89 } 89 }
90 // compiler targets 90 // compiler targets
91 for(int cct=0;cct<sizeof(cc_targets)/sizeof(*cc_targets);cct++) { 91 for(int cct=0;cct<sizeof(cc_targets)/sizeof(*cc_targets);cct++) {
92 try { 92 try {
93 string nos = strip_suffix(dp,cc_targets[cct]); 93 string nos = strip_suffix(dp,cc_targets[cct]);
94 string noro = strip_prefix(nos,root_intermediate); 94 string noro = strip_prefix(nos,root_intermediate);
95 deps.push_back(root_intermediate+noro+".cc"); 95 deps.push_back(root_intermediate+noro+".cc");
96 config_options *co_cpp_deps = config.lookup_config(noro,config_options::flag_cpp_deps); 96 config_options *co_cpp_deps = config.lookup_config(noro,config_options::flag_cpp_deps);
97 if( (!co_cpp_deps) || co_cpp_deps->cpp_deps) { 97 if( (!co_cpp_deps) || co_cpp_deps->cpp_deps) {
98 ifstream df((root_intermediate+noro+".d").c_str(),ios::in); 98 ifstream df((root_intermediate+noro+".d").c_str(),ios::in);
99 if(df.good()) { 99 if(df.good()) {
100 string str; 100 string str;
101 while(!df.eof()) { 101 while(!df.eof()) {
102 df >> str; 102 df >> str;
103 if(str.find_first_of("\\:")==string::npos) 103 if(str.find_first_of("\\:")==string::npos)
104 deps.push_back(combine_path(config.root_source+noro,str)); 104 deps.push_back(combine_path(config.root_source+noro,str));
105 } 105 }
106 } 106 }
107 } 107 }
108 // XXX: extra deps like IntermediateDeps? 108 // XXX: extra deps like IntermediateDeps?
109 }catch(utility_no_affix& una) { 109 }catch(utility_no_affix& una) {
110 // do nothing. 110 // do nothing.
111 } 111 }
112 } 112 }
113 } 113 }
114 114
115 bool component_factory::is_uptodate(const string& dst,file_list_t *deps) { 115 bool component_factory::is_uptodate(const string& dst,file_list_t *deps) {
116 string dp = normalize_path(dst,strip_trailing_slash); 116 string dp = normalize_path(dst,strip_trailing_slash);
117 // XXX: or just compare it off, instead of throwing things around. 117 // XXX: or just compare it off, instead of throwing things around.
118 try { 118 try {
119 strip_prefix(dp,root_intermediate); 119 string noro = strip_prefix(dp,root_intermediate);
120 return file_factory::is_uptodate(dst,deps); 120 for(int ppt=0;(ppt+1)<sizeof(pp_targets)/sizeof(*pp_targets);ppt++) {
121 }catch(utility_no_prefix& unp) { 121 try {
122 string nos = strip_suffix(noro,pp_targets[ppt]);
123 return file_factory::is_uptodate(root_intermediate+nos+".pp_stamp",deps);
124 }catch(utility_no_suffix& uns) { }
122 } 125 }
126 bool rv = file_factory::is_uptodate(dst,deps);
127 return rv;
128 }catch(utility_no_prefix& unp) { }
123 try { 129 try {
124 strip_prefix(dp,root_so); 130 strip_prefix(dp,root_so);
125 return file_factory::is_uptodate(dst,deps); 131 return file_factory::is_uptodate(dst,deps);
126 }catch(utility_no_prefix& unp) { 132 }catch(utility_no_prefix& unp) { }
127 }
128 return true; 133 return true;
129 } 134 }
130 135
131 void component_factory::build(const string& dst) { 136 void component_factory::build(const string& dst) {
132 string dp = normalize_path(dst,strip_trailing_slash); 137 string dp = normalize_path(dst,strip_trailing_slash);
133 // sources 138 // sources
134 try { 139 try {
135 string noro = strip_prefix(dp,root_source); 140 string noro = strip_prefix(dp,root_source);
136 // building the sources is left up to developer 141 // building the sources is left up to developer
137 return; 142 return;
138 }catch(utility_no_prefix& unp) { 143 }catch(utility_no_prefix& unp) {
139 } 144 }
140 // .so files 145 // .so files
141 try { 146 try {
142 string noso = strip_suffix(dp,".so"); 147 string noso = strip_suffix(dp,".so");
143 string noro = strip_prefix(noso,root_so); 148 string noro = strip_prefix(noso,root_so);
144 string o = root_intermediate+noro+".o"; 149 string o = root_intermediate+noro+".o";
145 if(access(o.c_str(),R_OK)) 150 if(access(o.c_str(),R_OK))
146 throw konforka::exception(CODEPOINT,string("can't access compiled component code (")+o+")"); 151 throw konforka::exception(CODEPOINT,string("can't access compiled component code (")+o+")");
147 make_path(dir_name(root_so+noro),0755); 152 make_path(dir_name(root_so+noro),0755);
148 file_lock lock_cc(root_intermediate+noro+".o.lock"); 153 file_lock lock_cc(root_intermediate+noro+".o.lock");
149 file_lock lock_so(root_so+noro+".so.lock"); 154 file_lock lock_so(root_so+noro+".so.lock");
150 int stdO = open((root_intermediate+noro+".ld.stdout").c_str(),O_CREAT|O_TRUNC|O_WRONLY,0664); 155 int stdO = open((root_intermediate+noro+".ld.stdout").c_str(),O_CREAT|O_TRUNC|O_WRONLY,0664);
151 if(stdO<0) 156 if(stdO<0)
152 throw konforka::exception(CODEPOINT,"failed to open/create linker stdout"); 157 throw konforka::exception(CODEPOINT,"failed to open/create linker stdout");
153 int stdE = open((root_intermediate+noro+".ld.stderr").c_str(),O_CREAT|O_TRUNC|O_WRONLY,0664); 158 int stdE = open((root_intermediate+noro+".ld.stderr").c_str(),O_CREAT|O_TRUNC|O_WRONLY,0664);
154 if(stdE<0) { 159 if(stdE<0) {
155 close(stdO); 160 close(stdO);
156 throw konforka::exception(CODEPOINT,"failed to open/create linker stderr"); 161 throw konforka::exception(CODEPOINT,"failed to open/create linker stderr");
157 } 162 }
158 list<string> args; 163 list<string> args;
159 config_options *co_ld_flags = config.lookup_config(noro,config_options::flag_ld_flags); 164 config_options *co_ld_flags = config.lookup_config(noro,config_options::flag_ld_flags);
160 if(co_ld_flags) { 165 if(co_ld_flags) {
161 args.insert(args.end(),co_ld_flags->ld_flags.begin(),co_ld_flags->ld_flags.end()); 166 args.insert(args.end(),co_ld_flags->ld_flags.begin(),co_ld_flags->ld_flags.end());
162 } 167 }
163 args.push_back("-shared"); 168 args.push_back("-shared");
164 args.push_back(o); 169 args.push_back(o);
165 file_list_t ancestors; 170 file_list_t ancestors;
166 get_ancestors(noro,ancestors); 171 get_ancestors(noro,ancestors);
167 for(file_list_t::const_iterator i=ancestors.begin();i!=ancestors.end();++i) { 172 for(file_list_t::const_iterator i=ancestors.begin();i!=ancestors.end();++i) {
168 string aso=root_so+*i+".so"; 173 string aso=root_so+*i+".so";
169 make(aso); 174 make(aso);
170 args.push_back(aso); 175 args.push_back(aso);
171 } 176 }
172 args.push_back("-o"); args.push_back(dp); 177 args.push_back("-o"); args.push_back(dp);
173 // TODO: "g++" configurable 178 // TODO: "g++" configurable
174 int rv = execute("g++",args,stdO,stdE); 179 int rv = execute("g++",args,stdO,stdE);
175 if(! (WIFEXITED(rv) && !WEXITSTATUS(rv)) ) 180 if(! (WIFEXITED(rv) && !WEXITSTATUS(rv)) )
@@ -204,96 +209,98 @@ namespace sitecing {
204 } 209 }
205 list<string> args; 210 list<string> args;
206 config_options *co_cpp_flags = config.lookup_config(noro,config_options::flag_cpp_flags); 211 config_options *co_cpp_flags = config.lookup_config(noro,config_options::flag_cpp_flags);
207 if(co_cpp_flags) { 212 if(co_cpp_flags) {
208 args.insert(args.end(),co_cpp_flags->cpp_flags.begin(),co_cpp_flags->cpp_flags.end()); 213 args.insert(args.end(),co_cpp_flags->cpp_flags.begin(),co_cpp_flags->cpp_flags.end());
209 } 214 }
210 // TODO: maybe move it to separare config option like CoreCPPFLags? 215 // TODO: maybe move it to separare config option like CoreCPPFLags?
211 args.push_back("-I"+root_intermediate); 216 args.push_back("-I"+root_intermediate);
212 args.push_back("-I"+root_source); 217 args.push_back("-I"+root_source);
213 args.push_back("-MD"); args.push_back("-MF"); args.push_back(root_intermediate+noro+".d"); 218 args.push_back("-MD"); args.push_back("-MF"); args.push_back(root_intermediate+noro+".d");
214 args.push_back("-c"); 219 args.push_back("-c");
215 args.push_back(cc); 220 args.push_back(cc);
216 args.push_back("-o"); args.push_back(o); 221 args.push_back("-o"); args.push_back(o);
217 // TODO: "g++" configurable 222 // TODO: "g++" configurable
218 int rv = execute("g++",args,stdO,stdE); 223 int rv = execute("g++",args,stdO,stdE);
219 if(! (WIFEXITED(rv) && !WEXITSTATUS(rv)) ) 224 if(! (WIFEXITED(rv) && !WEXITSTATUS(rv)) )
220 throw compile_error(CODEPOINT,"failed to compile component",noro); 225 throw compile_error(CODEPOINT,"failed to compile component",noro);
221 return; 226 return;
222 }catch(utility_no_affix& una) { 227 }catch(utility_no_affix& una) {
223 // do nothing, not a compiler target 228 // do nothing, not a compiler target
224 } 229 }
225 } 230 }
226 // preprocessor targets 231 // preprocessor targets
227 for(int ppt=0;ppt<sizeof(pp_targets)/sizeof(*pp_targets);ppt++) { 232 for(int ppt=0;ppt<sizeof(pp_targets)/sizeof(*pp_targets);ppt++) {
228 try { 233 try {
229 string nos = strip_suffix(dp,pp_targets[ppt]); 234 string nos = strip_suffix(dp,pp_targets[ppt]);
230 string noro = strip_prefix(nos,root_intermediate); 235 string noro = strip_prefix(nos,root_intermediate);
231 string src = root_source+noro; 236 string src = root_source+noro;
232 if(access(src.c_str(),R_OK)) 237 if(access(src.c_str(),R_OK))
233 throw konforka::exception(CODEPOINT,string("can't access component source (")+src+")"); 238 throw konforka::exception(CODEPOINT,string("can't access component source (")+src+")");
234 make_path(dir_name(root_intermediate+noro),0755); 239 make_path(dir_name(root_intermediate+noro),0755);
235 file_lock lock(root_intermediate+noro+".lock"); 240 file_lock lock(root_intermediate+noro+".lock");
236 sitecing_parser parser(*this); 241 sitecing_parser parser(*this);
237 config_options *co_skeleton = config.lookup_config(noro,config_options::flag_skeleton); 242 config_options *co_skeleton = config.lookup_config(noro,config_options::flag_skeleton);
238 if(co_skeleton) 243 if(co_skeleton)
239 parser.skeleton = co_skeleton->skeleton; 244 parser.skeleton = co_skeleton->skeleton;
240 static const char *id_chars = "abcdefghijklmnopqrstuvwxyz0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZ"; 245 static const char *id_chars = "abcdefghijklmnopqrstuvwxyz0123456789_ABCDEFGHIJKLMNOPQRSTUVWXYZ";
241 parser.class_name = normalize_path(noro,strip_leading_slash|strip_trailing_slash); 246 parser.class_name = normalize_path(noro,strip_leading_slash|strip_trailing_slash);
242 for(string::size_type illc = parser.class_name.find_first_not_of(id_chars);illc!=string::npos;illc=parser.class_name.find_first_not_of(id_chars,illc)) { 247 for(string::size_type illc = parser.class_name.find_first_not_of(id_chars);illc!=string::npos;illc=parser.class_name.find_first_not_of(id_chars,illc)) {
243 string::size_type lc = parser.class_name.find_first_of(id_chars,illc); 248 string::size_type lc = parser.class_name.find_first_of(id_chars,illc);
244 int n = ((lc==string::npos)?parser.class_name.length():lc)-illc; 249 int n = ((lc==string::npos)?parser.class_name.length():lc)-illc;
245 parser.class_name.replace(illc,n,n,'_'); 250 parser.class_name.replace(illc,n,n,'_');
246 } 251 }
247 parser.class_name = "_SCC_"+parser.class_name; 252 parser.class_name = "_SCC_"+parser.class_name;
248 parser.output_basename = nos; 253 parser.output_basename = nos;
249 parser.component_basename = noro; 254 parser.component_basename = noro;
250 try { 255 try {
251 parser.preprocess(src); 256 parser.preprocess(src);
257 string sf = root_intermediate+noro+".pp_stamp";
258 ofstream sfs(sf.c_str(),ios::trunc|ios::out); // touch .pp_stamp
252 }catch(preprocessor_error& pe) { 259 }catch(preprocessor_error& pe) {
253 pe.component_name = noro; 260 pe.component_name = noro;
254 pe.see(CODEPOINT); 261 pe.see(CODEPOINT);
255 throw; 262 throw;
256 } 263 }
257 return; 264 return;
258 }catch(utility_no_affix& una) { 265 }catch(utility_no_affix& una) {
259 // must be a crap from .d file 266 // must be a crap from .d file
260 } 267 }
261 } 268 }
262 cerr << "ignoring build request for " << dp << endl; 269 cerr << "ignoring build request for " << dp << endl;
263 } 270 }
264 271
265 int component_factory::execute(const string& cmd, const list<string>& args,int stdo,int stde) { 272 int component_factory::execute(const string& cmd, const list<string>& args,int stdo,int stde) {
266 // XXX: is it right that we do stdio/stderr tricks outside of the function? 273 // XXX: is it right that we do stdio/stderr tricks outside of the function?
267 cerr << "executing: " << cmd; 274 cerr << "executing: " << cmd;
268 vector<const char*> argv(args.size()+2); 275 vector<const char*> argv(args.size()+2);
269 argv[0]=cmd.c_str(); 276 argv[0]=cmd.c_str();
270 int an = 1; 277 int an = 1;
271 for(list<string>::const_iterator i=args.begin();i!=args.end();i++) { 278 for(list<string>::const_iterator i=args.begin();i!=args.end();i++) {
272 cerr << " " << *i ; 279 cerr << " " << *i ;
273 argv[an++] = i->c_str(); 280 argv[an++] = i->c_str();
274 } 281 }
275 cerr << endl; 282 cerr << endl;
276 argv[an++]=NULL; 283 argv[an++]=NULL;
277 pid_t pid = vfork(); 284 pid_t pid = vfork();
278 if(pid==-1) { 285 if(pid==-1) {
279 close(stdo); close(stde); 286 close(stdo); close(stde);
280 throw konforka::exception(CODEPOINT,"failed to vfork()"); 287 throw konforka::exception(CODEPOINT,"failed to vfork()");
281 } 288 }
282 if(!pid) { 289 if(!pid) {
283 // child 290 // child
284 if(dup2(stdo,1)!=1) 291 if(dup2(stdo,1)!=1)
285 _exit(-1); 292 _exit(-1);
286 if(dup2(stde,2)!=2) 293 if(dup2(stde,2)!=2)
287 _exit(-1); 294 _exit(-1);
288 close(0); 295 close(0);
289 execvp(cmd.c_str(),(char**)&argv.front()); 296 execvp(cmd.c_str(),(char**)&argv.front());
290 _exit(-1); 297 _exit(-1);
291 } 298 }
292 // parent 299 // parent
293 close(stdo); close(stde); 300 close(stdo); close(stde);
294 int rv; 301 int rv;
295 if(waitpid(pid,&rv,0)<0) 302 if(waitpid(pid,&rv,0)<0)
296 throw konforka::exception(CODEPOINT,"failed to waitpid()"); 303 throw konforka::exception(CODEPOINT,"failed to waitpid()");
297 return rv; 304 return rv;
298 } 305 }
299 306