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