1 //
2 // aegis - project change supervisor
3 // Copyright (C) 1991-2009, 2011, 2012 Peter Miller
4 // Copyright (C) 2008, 2009 Walter Franzini
5 //
6 // This program is free software; you can redistribute it and/or modify
7 // it under the terms of the GNU General Public License as published by
8 // the Free Software Foundation; either version 3 of the License, or (at
9 // your option) any later version.
10 //
11 // This program is distributed in the hope that it will be useful,
12 // but WITHOUT ANY WARRANTY; without even the implied warranty of
13 // MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU
14 // General Public License for more details.
15 //
16 // You should have received a copy of the GNU General Public License
17 // along with this program. If not, see <http://www.gnu.org/licenses/>.
18 //
19
20 #include <common/ac/assert.h>
21 #include <common/ac/errno.h>
22 #include <common/ac/libintl.h>
23 #include <common/ac/stdio.h>
24 #include <common/ac/stdlib.h>
25 #include <common/ac/string.h>
26 #include <common/ac/time.h>
27
28 #include <common/progname.h>
29 #include <common/quit.h>
30 #include <common/sizeof.h>
31 #include <common/str_list.h>
32 #include <common/trace.h>
33
34 #include <libaegis/ael/change/by_state.h>
35 #include <libaegis/arglex/change.h>
36 #include <libaegis/arglex/project.h>
37 #include <libaegis/arglex2.h>
38 #include <libaegis/change.h>
39 #include <libaegis/change/branch.h>
40 #include <libaegis/change/file.h>
41 #include <libaegis/change/identifier.h>
42 #include <libaegis/col.h>
43 #include <libaegis/commit.h>
44 #include <libaegis/help.h>
45 #include <libaegis/lock.h>
46 #include <libaegis/log.h>
47 #include <libaegis/os.h>
48 #include <libaegis/project.h>
49 #include <libaegis/search_path/base_get.h>
50 #include <libaegis/sub.h>
51 #include <libaegis/user.h>
52
53 #include <aegis/aeb.h>
54
55 //
56 // NAME
57 // build_usage
58 //
59 // SYNOPSIS
60 // void build_usage(void);
61 //
62 // DESCRIPTION
63 // The build_usage function is used to
64 // briefly describe how to used the 'aegis -Build' command.
65 //
66
67 static void
build_usage(void)68 build_usage(void)
69 {
70 const char *progname;
71
72 progname = progname_get();
73 fprintf(stderr, "usage: %s -Build [ <option>... ]\n", progname);
74 fprintf(stderr, " %s -Build -List [ <option>... ]\n", progname);
75 fprintf(stderr, " %s -Build -Help\n", progname);
76 quit(1);
77 }
78
79
80 //
81 // NAME
82 // build_help
83 //
84 // SYNOPSIS
85 // void build_help(void);
86 //
87 // DESCRIPTION
88 // The build_help function is used to
89 // describe in detail how to use the 'aegis -Build' command.
90 //
91
92 static void
build_help(void)93 build_help(void)
94 {
95 help("aeb", build_usage);
96 }
97
98
99 //
100 // NAME
101 // build_list
102 //
103 // SYNOPSIS
104 // void build_list(void);
105 //
106 // DESCRIPTION
107 // The build_list function is used to
108 // list the changes which may be built within the project.
109 //
110
111 static void
build_list(void)112 build_list(void)
113 {
114 trace(("build_list()\n{\n"));
115 arglex();
116 change_identifier cid;
117 cid.command_line_parse_rest(build_usage);
118 list_changes_in_state_mask
119 (
120 cid,
121 (
122 (1 << cstate_state_being_developed)
123 |
124 (1 << cstate_state_being_integrated)
125 )
126 );
127 trace(("}\n"));
128 }
129
130
131 //
132 // NAME
133 // build_main
134 //
135 // SYNOPSIS
136 // void build_main(void);
137 //
138 // DESCRIPTION
139 // The build_main function is used to build a change in the "being
140 // developed" or "being integrated" states. It extracts what to
141 // do from the command line.
142 //
143
144 static void
build_main(void)145 build_main(void)
146 {
147 cstate_ty *cstate_data;
148 pconf_ty *pconf_data;
149 log_style_ty log_style;
150 string_ty *s1;
151 string_ty *s2;
152 bool minimum;
153
154 trace(("build_main()\n{\n"));
155 change_identifier cid;
156 arglex();
157 log_style = log_style_snuggle_default;
158 minimum = false;
159 string_list_ty partial;
160 while (arglex_token != arglex_token_eoln)
161 {
162 switch (arglex_token)
163 {
164 default:
165 generic_argument(build_usage);
166 continue;
167
168 case arglex_token_change:
169 case arglex_token_number:
170 case arglex_token_project:
171 cid.command_line_parse(build_usage);
172 continue;
173
174 case arglex_token_file:
175 if (arglex() != arglex_token_string)
176 option_needs_files(arglex_token_file, build_usage);
177 // fall through...
178
179 case arglex_token_string:
180 s2 = str_from_c(arglex_value.alv_string);
181 partial.push_back(s2);
182 str_free(s2);
183 break;
184
185 case arglex_token_nolog:
186 if (log_style == log_style_none)
187 duplicate_option(build_usage);
188 log_style = log_style_none;
189 break;
190
191 case arglex_token_minimum:
192 if (minimum)
193 duplicate_option(build_usage);
194 minimum = true;
195 break;
196
197 case arglex_token_wait:
198 case arglex_token_wait_not:
199 user_ty::lock_wait_argument(build_usage);
200 break;
201
202 case arglex_token_symbolic_links:
203 case arglex_token_symbolic_links_not:
204 user_ty::symlink_pref_argument(build_usage);
205 break;
206
207 case arglex_token_base_relative:
208 case arglex_token_current_relative:
209 user_ty::relative_filename_preference_argument(build_usage);
210 break;
211 }
212 arglex();
213 }
214 cid.command_line_check(build_usage);
215
216 //
217 // Take an advisory write lock on this row of the change table.
218 // Block if necessary.
219 //
220 // Also take a read lock on the baseline, to ensure that it does
221 // not change (aeip) for the duration of the build.
222 //
223 if (!partial.nstrings)
224 change_cstate_lock_prepare(cid.get_cp());
225 project_baseline_read_lock_prepare(cid.get_pp());
226 lock_take();
227 cstate_data = cid.get_cp()->cstate_get();
228
229 //
230 // Extract the appropriate row of the change table.
231 // It is an error if the change is not in one of the being_developed
232 // or being_integrated states.
233 // It is an error if the change is not assigned to the current user.
234 // It is an error if the change has no files assigned.
235 //
236 bool integrating = false;
237 switch (cstate_data->state)
238 {
239 case cstate_state_awaiting_development:
240 case cstate_state_awaiting_integration:
241 case cstate_state_awaiting_review:
242 case cstate_state_being_reviewed:
243 case cstate_state_completed:
244 #ifndef DEBUG
245 default:
246 #endif
247 change_fatal(cid.get_cp(), 0, i18n("bad build state"));
248 break;
249
250 case cstate_state_being_developed:
251 if (cid.get_cp()->is_a_branch())
252 change_fatal(cid.get_cp(), 0, i18n("bad branch build"));
253 if
254 (
255 nstring(cid.get_cp()->developer_name())
256 !=
257 cid.get_up()->name()
258 )
259 change_fatal(cid.get_cp(), 0, i18n("not developer"));
260 if (!change_file_nth(cid.get_cp(), (size_t)0, view_path_first))
261 change_fatal(cid.get_cp(), 0, i18n("no files"));
262 break;
263
264 case cstate_state_being_integrated:
265 if
266 (
267 nstring(cid.get_cp()->integrator_name())
268 !=
269 cid.get_up()->name()
270 )
271 change_fatal(cid.get_cp(), 0, i18n("not integrator"));
272 if (partial.nstrings)
273 change_fatal(cid.get_cp(), 0, i18n("bad build, partial"));
274 integrating = true;
275 break;
276 }
277
278 if
279 (
280 !integrating
281 &&
282 (
283 cid.get_cp()->run_project_file_command_needed()
284 ||
285 (!partial.nstrings && cid.get_cp()->file_promote())
286 )
287 )
288 {
289 //
290 // Remember the fact that we are about to run the
291 // project_file_command, but we actually run it outside the
292 // locks, so that it can use the up-to-date lists of change
293 // files and project files.
294 //
295 cid.get_cp()->run_project_file_command_done();
296
297 //
298 // Write out the file state, and then let go of the locks
299 // and take them again. This ensures the data is consistent
300 // for the next stage of processing.
301 //
302 trace(("Write out what we've done so far.\n"));
303 cid.get_cp()->cstate_write();
304 commit();
305 lock_release();
306
307 //
308 // Either the project files list changed, or the change's files
309 // list changed (which can change the project files list seen
310 // from this change) or both.
311 //
312 cid.get_cp()->run_project_file_command(cid.get_up());
313
314 trace(("Take the locks again.\n"));
315 change_cstate_lock_prepare(cid.get_cp());
316 project_baseline_read_lock_prepare(cid.get_pp());
317 lock_take();
318 }
319 cstate_data = cid.get_cp()->cstate_get();
320
321 //
322 // If no build is required, we stop here.
323 // Note that this is *after* the symlinks have been repaired.
324 //
325 if (!change_build_required(cid.get_cp()))
326 {
327 change_verbose(cid.get_cp(), 0, i18n("no build required"));
328 quit(0);
329 }
330
331 //
332 // Resolve relative filenames into project filenames.
333 // Do this after we know the change is in a buildable state.
334 //
335 if (partial.nstrings)
336 {
337 nstring_list search_path;
338 size_t j;
339 size_t k;
340
341 //
342 // Search path for resolving file names.
343 //
344 cid.get_cp()->search_path_get(search_path, true);
345
346 //
347 // Find the base for relative filenames.
348 //
349 nstring base = search_path_base_get(search_path, cid.get_up());
350
351 //
352 // resolve the path of each file
353 // 1. the absolute path of the file name is obtained
354 // 2. if the file is inside the development directory, ok
355 // 3. if the file is inside the baseline, ok
356 // 4. if neither, error
357 //
358 string_list_ty wl2;
359 for (j = 0; j < partial.nstrings; ++j)
360 {
361 //
362 // leave variable assignments alone
363 //
364 s1 = partial.string[j];
365 if (strchr(s1->str_text, '='))
366 {
367 wl2.push_back(s1);
368 continue;
369 }
370
371 //
372 // resolve relative paths
373 //
374 if (s1->str_text[0] == '/')
375 s2 = str_copy(s1);
376 else
377 s2 = os_path_join(base.get_ref(), s1);
378 cid.get_up()->become_begin();
379 s1 = os_pathname(s2, 0);
380 cid.get_up()->become_end();
381 str_free(s2);
382 s2 = 0;
383 for (k = 0; k < search_path.size(); ++k)
384 {
385 s2 = os_below_dir(search_path[k].get_ref(), s1);
386 if (s2)
387 break;
388 }
389 str_free(s1);
390 if (!s2)
391 {
392 sub_context_ty *scp;
393
394 scp = sub_context_new();
395 sub_var_set_string(scp, "File_Name", partial.string[j]);
396 change_fatal(cid.get_cp(), scp, i18n("$filename unrelated"));
397 // NOTREACHED
398 sub_context_delete(scp);
399 }
400
401 //
402 // make sure it's unique
403 //
404 if (wl2.member(s2))
405 {
406 sub_context_ty *scp;
407
408 scp = sub_context_new();
409 sub_var_set_string(scp, "File_Name", s2);
410 change_fatal(cid.get_cp(), scp, i18n("too many $filename"));
411 // NOTREACHED
412 sub_context_delete(scp);
413 }
414 else
415 wl2.push_back(s2);
416 str_free(s2);
417 }
418 partial = wl2;
419 }
420
421 //
422 // It is an error if the change attributes include architectures
423 // not in the project.
424 //
425 change_check_architectures(cid.get_cp());
426
427 //
428 // Update the time the build was done.
429 // This will not be written out if the build fails.
430 //
431 os_throttle();
432 change_build_time_set(cid.get_cp());
433
434 //
435 // get the command to execute
436 // 1. if the change is editing config, use that
437 // 2. if the baseline contains config, use that
438 // 3. error if can't find one (DON'T look for file existence)
439 //
440 pconf_data = change_pconf_get(cid.get_cp(), 1);
441
442 //
443 // If aeib had a -minimum, then aeb implicitly does
444 //
445 if
446 (
447 cstate_data->state == cstate_state_being_integrated
448 &&
449 cstate_data->minimum_integration
450 )
451 {
452 minimum = true;
453 }
454
455 //
456 // the program has changed, so it needs testing again,
457 // so stomp on the validation fields.
458 //
459 trace(("nuke time stamps\n"));
460 change_test_times_clear(cid.get_cp());
461
462 //
463 // do the build
464 //
465 trace(("open the log file\n"));
466 trace(("do the build\n"));
467 if (cstate_data->state == cstate_state_being_integrated)
468 {
469 user_ty::pointer pup = project_user(cid.get_pp());
470 log_open(change_logfile_get(cid.get_cp()), pup, log_style);
471
472 assert(pconf_data->integration_directory_style);
473 work_area_style_ty style = *pconf_data->integration_directory_style;
474 if (minimum || style.derived_at_start_only)
475 {
476 style.derived_file_link = false;
477 style.derived_file_symlink = false;
478 style.derived_file_copy = false;
479 }
480 change_create_symlinks_to_baseline(cid.get_cp(), pup, style);
481
482 change_verbose(cid.get_cp(), 0, i18n("integration build started"));
483 change_run_build_command(cid.get_cp());
484 change_verbose(cid.get_cp(), 0, i18n("integration build complete"));
485
486 change_remove_symlinks_to_baseline(cid.get_cp(), pup, style);
487 }
488 else
489 {
490 log_open(change_logfile_get(cid.get_cp()), cid.get_up(), log_style);
491
492 assert(pconf_data->development_directory_style);
493 work_area_style_ty style = *pconf_data->development_directory_style;
494 if (minimum || style.derived_at_start_only)
495 {
496 style.derived_file_link = false;
497 style.derived_file_symlink = false;
498 style.derived_file_copy = false;
499 }
500 if
501 (
502 style.source_file_link
503 ||
504 style.source_file_symlink
505 ||
506 style.source_file_copy
507 ||
508 style.derived_file_link
509 ||
510 style.derived_file_symlink
511 ||
512 style.derived_file_copy
513 )
514 {
515 bool verify_dflt =
516 (
517 style.during_build_only
518 ||
519 cid.get_cp()->run_project_file_command_needed()
520 );
521 if (cid.get_up()->symlink_pref(verify_dflt))
522 {
523 change_create_symlinks_to_baseline
524 (
525 cid.get_cp(),
526 cid.get_up(),
527 style
528 );
529 }
530 }
531
532 if (partial.nstrings)
533 change_verbose(cid.get_cp(), 0, i18n("partial build started"));
534 else
535 change_verbose(cid.get_cp(), 0, i18n("development build started"));
536
537 change_run_development_build_command
538 (
539 cid.get_cp(),
540 cid.get_up(),
541 &partial
542 );
543 if (partial.nstrings)
544 change_verbose(cid.get_cp(), 0, i18n("partial build complete"));
545 else
546 change_verbose(cid.get_cp(), 0, i18n("development build complete"));
547
548 //
549 // This looks unconditional. The conditional logic is within
550 // the function, rather than out here.
551 //
552 change_remove_symlinks_to_baseline(cid.get_cp(), cid.get_up(), style);
553 }
554
555 //
556 // Update change data with result of build.
557 // (This will be used when validating developer sign off.)
558 // Release advisory write lock on row of change table.
559 //
560 if (!partial.nstrings)
561 {
562 change_file_list_metrics_check(cid.get_cp());
563 cid.get_cp()->cstate_write();
564 commit();
565 }
566 lock_release();
567 trace(("}\n"));
568 }
569
570
571 //
572 // NAME
573 // build
574 //
575 // SYNOPSIS
576 // void build(void);
577 //
578 // DESCRIPTION
579 // The build function is used to
580 // dispatch the 'aegis -Build' command to the relevant functionality.
581 // Where it goes depends on the command line.
582 //
583
584 void
build(void)585 build(void)
586 {
587 static arglex_dispatch_ty dispatch[] =
588 {
589 {arglex_token_help, build_help, 0 },
590 {arglex_token_list, build_list, 0 },
591 };
592
593 trace(("build()\n{\n"));
594 arglex_dispatch(dispatch, SIZEOF(dispatch), build_main);
595 trace(("}\n"));
596 }
597
598
599 // vim: set ts=8 sw=4 et :
600