| line | stmt | bran | cond | sub | pod | time | code | 
| 1 |  |  |  |  |  |  | # App::hopen: Implementation of the hopen(1) program | 
| 2 |  |  |  |  |  |  | package App::hopen; | 
| 3 |  |  |  |  |  |  | our $VERSION = '0.000012'; # TRIAL | 
| 4 |  |  |  |  |  |  |  | 
| 5 |  |  |  |  |  |  | # Imports {{{1 | 
| 6 | 2 |  |  | 2 |  | 211676 | use strict; use warnings; | 
|  | 2 |  |  | 2 |  | 12 |  | 
|  | 2 |  |  |  |  | 57 |  | 
|  | 2 |  |  |  |  | 20 |  | 
|  | 2 |  |  |  |  | 5 |  | 
|  | 2 |  |  |  |  | 64 |  | 
| 7 | 4 |  |  | 2 |  | 448 | use Data::Hopen::Base; | 
|  | 4 |  |  |  |  | 15772 |  | 
|  | 4 |  |  |  |  | 22 |  | 
| 8 |  |  |  |  |  |  |  | 
| 9 | 4 |  |  | 2 |  | 3573 | use App::hopen::AppUtil ':all'; | 
|  | 4 |  |  |  |  | 1291 |  | 
|  | 4 |  |  |  |  | 254 |  | 
| 10 | 4 |  |  | 2 |  | 2025 | use App::hopen::BuildSystemGlobals; | 
|  | 2 |  |  |  |  | 6 |  | 
|  | 2 |  |  |  |  | 259 |  | 
| 11 | 4 |  |  | 2 |  | 902 | use App::hopen::Phases qw(:default phase_idx next_phase); | 
|  | 2 |  |  |  |  | 8 |  | 
|  | 2 |  |  |  |  | 295 |  | 
| 12 | 2 |  |  | 2 |  | 894 | use App::hopen::Util::String qw(line_mark_string); | 
|  | 2 |  |  |  |  | 5 |  | 
|  | 2 |  |  |  |  | 127 |  | 
| 13 | 2 |  |  | 2 |  | 14 | use Data::Hopen qw(:default loadfrom isMYH MYH $VERBOSE $QUIET); | 
|  | 2 |  |  |  |  | 4 |  | 
|  | 2 |  |  |  |  | 258 |  | 
| 14 | 2 |  |  | 2 |  | 842 | use Data::Hopen::Scope::Hash; | 
|  | 2 |  |  |  |  | 72197 |  | 
|  | 2 |  |  |  |  | 108 |  | 
| 15 | 2 |  |  | 2 |  | 895 | use Data::Hopen::Scope::Environment; | 
|  | 2 |  |  |  |  | 4050 |  | 
|  | 2 |  |  |  |  | 97 |  | 
| 16 | 2 |  |  | 2 |  | 15 | use Data::Hopen::Util::Data qw(dedent forward_opts); | 
|  | 2 |  |  |  |  | 4 |  | 
|  | 2 |  |  |  |  | 88 |  | 
| 17 | 2 |  |  | 2 |  | 11 | use Data::Dumper; | 
|  | 2 |  |  |  |  | 5 |  | 
|  | 2 |  |  |  |  | 69 |  | 
| 18 | 2 |  |  | 2 |  | 916 | use File::Path::Tiny; | 
|  | 2 |  |  |  |  | 2181 |  | 
|  | 2 |  |  |  |  | 61 |  | 
| 19 | 2 |  |  | 2 |  | 14 | use File::stat (); | 
|  | 2 |  |  |  |  | 3 |  | 
|  | 2 |  |  |  |  | 41 |  | 
| 20 | 2 |  |  | 2 |  | 1373 | use Getopt::Long qw(GetOptionsFromArray :config gnu_getopt); | 
|  | 2 |  |  |  |  | 21350 |  | 
|  | 2 |  |  |  |  | 10 |  | 
| 21 | 2 |  |  | 2 |  | 444 | use Hash::Merge; | 
|  | 2 |  |  |  |  | 4 |  | 
|  | 2 |  |  |  |  | 79 |  | 
| 22 | 2 |  |  | 2 |  | 10 | use Path::Class; | 
|  | 2 |  |  |  |  | 5 |  | 
|  | 2 |  |  |  |  | 99 |  | 
| 23 | 2 |  |  | 2 |  | 12 | use Scalar::Util qw(looks_like_number); | 
|  | 2 |  |  |  |  | 3 |  | 
|  | 2 |  |  |  |  | 119 |  | 
| 24 |  |  |  |  |  |  |  | 
| 25 | 2 |  |  | 2 |  | 89 | BEGIN { $Data::Dumper::Indent = 1; }    # DEBUG | 
| 26 |  |  |  |  |  |  |  | 
| 27 |  |  |  |  |  |  | # }}}1 | 
| 28 |  |  |  |  |  |  | # Documentation {{{1 | 
| 29 |  |  |  |  |  |  |  | 
| 30 |  |  |  |  |  |  | =pod | 
| 31 |  |  |  |  |  |  |  | 
| 32 |  |  |  |  |  |  | =encoding UTF-8 | 
| 33 |  |  |  |  |  |  |  | 
| 34 |  |  |  |  |  |  | =head1 NAME | 
| 35 |  |  |  |  |  |  |  | 
| 36 |  |  |  |  |  |  | App::hopen - Graph-driven cross-platform build system | 
| 37 |  |  |  |  |  |  |  | 
| 38 |  |  |  |  |  |  | =head1 CURRENT STATUS | 
| 39 |  |  |  |  |  |  |  | 
| 40 |  |  |  |  |  |  | Most features are not yet implemented ;) .  However it will generate a | 
| 41 |  |  |  |  |  |  | C<Makefile> or C<build.ninja> file for a C C<Hello, World> program at this | 
| 42 |  |  |  |  |  |  | point!  It can generate command lines for gcc(1) or for Microsoft's C<cl.exe>. | 
| 43 |  |  |  |  |  |  |  | 
| 44 |  |  |  |  |  |  | =head1 INTRODUCTION | 
| 45 |  |  |  |  |  |  |  | 
| 46 |  |  |  |  |  |  | hopen is a cross-platform software build generator.  It makes files you can | 
| 47 |  |  |  |  |  |  | pass to Make, Ninja, Visual Studio, or other build tools, to compile and | 
| 48 |  |  |  |  |  |  | link your software.  hopen gives you: | 
| 49 |  |  |  |  |  |  |  | 
| 50 |  |  |  |  |  |  | =over | 
| 51 |  |  |  |  |  |  |  | 
| 52 |  |  |  |  |  |  | =item * | 
| 53 |  |  |  |  |  |  |  | 
| 54 |  |  |  |  |  |  | A full, Turing-complete, robust programming language to write your | 
| 55 |  |  |  |  |  |  | build scripts (specifically, Perl 5.14+) | 
| 56 |  |  |  |  |  |  |  | 
| 57 |  |  |  |  |  |  | =item * | 
| 58 |  |  |  |  |  |  |  | 
| 59 |  |  |  |  |  |  | No hidden magic!  All your data is visible and accessible in a build graph | 
| 60 |  |  |  |  |  |  | (whence "graph-driven"). | 
| 61 |  |  |  |  |  |  |  | 
| 62 |  |  |  |  |  |  | =item * | 
| 63 |  |  |  |  |  |  |  | 
| 64 |  |  |  |  |  |  | Context-sensitivity.  Your users can tweak their own builds for their own | 
| 65 |  |  |  |  |  |  | platforms without affecting your project. | 
| 66 |  |  |  |  |  |  |  | 
| 67 |  |  |  |  |  |  | =back | 
| 68 |  |  |  |  |  |  |  | 
| 69 |  |  |  |  |  |  | See L<App::hopen::Conventions> for details of the input format. | 
| 70 |  |  |  |  |  |  |  | 
| 71 |  |  |  |  |  |  | Why Perl?  Because (1) you probably already have it installed, and | 
| 72 |  |  |  |  |  |  | (2) it is the original write-once, run-everywhere language! | 
| 73 |  |  |  |  |  |  |  | 
| 74 |  |  |  |  |  |  | =head2 Example | 
| 75 |  |  |  |  |  |  |  | 
| 76 |  |  |  |  |  |  | Create a file C<.hopen.pl> in your source tree.  Then: | 
| 77 |  |  |  |  |  |  |  | 
| 78 |  |  |  |  |  |  | $ hopen | 
| 79 |  |  |  |  |  |  | From ``.'' into ``built'' | 
| 80 |  |  |  |  |  |  | Running Check phase | 
| 81 |  |  |  |  |  |  |  | 
| 82 |  |  |  |  |  |  | Now C<built/MY.hopen.pl> has been created, and loaded with information about | 
| 83 |  |  |  |  |  |  | your configuration.  You can edit that file if you want to change what will | 
| 84 |  |  |  |  |  |  | happen next. | 
| 85 |  |  |  |  |  |  |  | 
| 86 |  |  |  |  |  |  | $ hopen | 
| 87 |  |  |  |  |  |  | From ``.'' into ``built'' | 
| 88 |  |  |  |  |  |  | Running Gen phase | 
| 89 |  |  |  |  |  |  |  | 
| 90 |  |  |  |  |  |  | Now C<built/Makefile> has been created. | 
| 91 |  |  |  |  |  |  |  | 
| 92 |  |  |  |  |  |  | $ hopen --build | 
| 93 |  |  |  |  |  |  | Building in foo/built | 
| 94 |  |  |  |  |  |  |  | 
| 95 |  |  |  |  |  |  | And your software is ready to go!  C<make> has been run in C<built/>, | 
| 96 |  |  |  |  |  |  | with output left in C<built/>. | 
| 97 |  |  |  |  |  |  |  | 
| 98 |  |  |  |  |  |  | See L<App::hopen::Conventions> for information on writing C<.hopen.pl> files. | 
| 99 |  |  |  |  |  |  |  | 
| 100 |  |  |  |  |  |  | =head1 SYNOPSIS | 
| 101 |  |  |  |  |  |  |  | 
| 102 |  |  |  |  |  |  | hopen [options] [--] [destination dir [project dir]] | 
| 103 |  |  |  |  |  |  |  | 
| 104 |  |  |  |  |  |  | If no project directory is specified, the current directory is used. | 
| 105 |  |  |  |  |  |  |  | 
| 106 |  |  |  |  |  |  | If no destination directory is specified, C<< <project dir>/built >> is used. | 
| 107 |  |  |  |  |  |  |  | 
| 108 |  |  |  |  |  |  | See L<App::hopen> and L<App::hopen::Conventions> for more details. | 
| 109 |  |  |  |  |  |  |  | 
| 110 |  |  |  |  |  |  | =head1 OPTIONS | 
| 111 |  |  |  |  |  |  |  | 
| 112 |  |  |  |  |  |  | =over | 
| 113 |  |  |  |  |  |  |  | 
| 114 |  |  |  |  |  |  | =item -a C<architecture> | 
| 115 |  |  |  |  |  |  |  | 
| 116 |  |  |  |  |  |  | Specify the architecture.  This is an arbitrary string interpreted by the | 
| 117 |  |  |  |  |  |  | generator or toolset. | 
| 118 |  |  |  |  |  |  |  | 
| 119 |  |  |  |  |  |  | =item -e C<Perl code> | 
| 120 |  |  |  |  |  |  |  | 
| 121 |  |  |  |  |  |  | Add the C<Perl code> as if it were a hopen file.  C<-e> files are processed | 
| 122 |  |  |  |  |  |  | after all other hopen files, so can modify anything that has been set up | 
| 123 |  |  |  |  |  |  | by those files.  Can be specified more than once. | 
| 124 |  |  |  |  |  |  |  | 
| 125 |  |  |  |  |  |  | =item --fresh | 
| 126 |  |  |  |  |  |  |  | 
| 127 |  |  |  |  |  |  | Start a fresh build --- ignore any C<MY.hopen.pl> file that may exist in | 
| 128 |  |  |  |  |  |  | the destination directory. | 
| 129 |  |  |  |  |  |  |  | 
| 130 |  |  |  |  |  |  | =item --from C<project dir> | 
| 131 |  |  |  |  |  |  |  | 
| 132 |  |  |  |  |  |  | Specify the project directory.  Overrides a project directory given as a | 
| 133 |  |  |  |  |  |  | positional argument. | 
| 134 |  |  |  |  |  |  |  | 
| 135 |  |  |  |  |  |  | =item -g C<generator> | 
| 136 |  |  |  |  |  |  |  | 
| 137 |  |  |  |  |  |  | Specify the generator.  The given C<generator> should be either a full package | 
| 138 |  |  |  |  |  |  | name or the part after C<App::hopen::Gen::>. | 
| 139 |  |  |  |  |  |  |  | 
| 140 |  |  |  |  |  |  | =item -t C<toolset> | 
| 141 |  |  |  |  |  |  |  | 
| 142 |  |  |  |  |  |  | Specify the toolset.  The given C<toolset> should be either a full package | 
| 143 |  |  |  |  |  |  | name or the part after C<App::hopen::T::>. | 
| 144 |  |  |  |  |  |  |  | 
| 145 |  |  |  |  |  |  | =item --to C<destination dir> | 
| 146 |  |  |  |  |  |  |  | 
| 147 |  |  |  |  |  |  | Specify the destination directory.  Overrides a destination directory given | 
| 148 |  |  |  |  |  |  | as a positional argument. | 
| 149 |  |  |  |  |  |  |  | 
| 150 |  |  |  |  |  |  | =item --phase C<phase> | 
| 151 |  |  |  |  |  |  |  | 
| 152 |  |  |  |  |  |  | Specify which phase of the process to run.  Note that this overrides whatever | 
| 153 |  |  |  |  |  |  | is specified in any MY.hopen.pl file, so may cause unexpected results! | 
| 154 |  |  |  |  |  |  |  | 
| 155 |  |  |  |  |  |  | If C<--phase> is given, no other hopen file can set the phase, and hopen will | 
| 156 |  |  |  |  |  |  | terminate if a file attempts to do so. | 
| 157 |  |  |  |  |  |  |  | 
| 158 |  |  |  |  |  |  | =item -q | 
| 159 |  |  |  |  |  |  |  | 
| 160 |  |  |  |  |  |  | Produce no output (quiet).  Overrides C<-v>. | 
| 161 |  |  |  |  |  |  |  | 
| 162 |  |  |  |  |  |  | =item -v, --verbose=n | 
| 163 |  |  |  |  |  |  |  | 
| 164 |  |  |  |  |  |  | Verbose.  Specify more C<v>'s for more verbosity.  At present, C<-vv> | 
| 165 |  |  |  |  |  |  | (equivalently, C<--verbose=2>) gives | 
| 166 |  |  |  |  |  |  | you detailed traces of the data, and C<-vvv> gives you more detailed | 
| 167 |  |  |  |  |  |  | code tracebacks on error. | 
| 168 |  |  |  |  |  |  |  | 
| 169 |  |  |  |  |  |  | =item --version | 
| 170 |  |  |  |  |  |  |  | 
| 171 |  |  |  |  |  |  | Print the version of hopen and exit | 
| 172 |  |  |  |  |  |  |  | 
| 173 |  |  |  |  |  |  | =back | 
| 174 |  |  |  |  |  |  |  | 
| 175 |  |  |  |  |  |  | =head1 INTERNALS | 
| 176 |  |  |  |  |  |  |  | 
| 177 |  |  |  |  |  |  | After the C<hopen> file is processed, cycles are detected and reported as | 
| 178 |  |  |  |  |  |  | errors.  *(TODO change this to support LaTeX multi-run files?)*  Then the DAG | 
| 179 |  |  |  |  |  |  | is traversed, and each operation writes the necessary information to the | 
| 180 |  |  |  |  |  |  | file being generated. | 
| 181 |  |  |  |  |  |  |  | 
| 182 |  |  |  |  |  |  | =cut | 
| 183 |  |  |  |  |  |  |  | 
| 184 |  |  |  |  |  |  | # }}}1 | 
| 185 |  |  |  |  |  |  | # Constants {{{1 | 
| 186 |  |  |  |  |  |  |  | 
| 187 | 2 |  |  | 2 |  | 12 | use constant DEBUG          => false; | 
|  | 2 |  |  |  |  | 8 |  | 
|  | 2 |  |  |  |  | 113 |  | 
| 188 |  |  |  |  |  |  |  | 
| 189 |  |  |  |  |  |  | # Shell exit codes | 
| 190 | 2 |  |  | 2 |  | 12 | use constant EXIT_OK        => 0;   # success | 
|  | 2 |  |  |  |  | 4 |  | 
|  | 2 |  |  |  |  | 82 |  | 
| 191 | 2 |  |  | 2 |  | 13 | use constant EXIT_PROC_ERR  => 1;   # error during processing | 
|  | 2 |  |  |  |  | 4 |  | 
|  | 2 |  |  |  |  | 75 |  | 
| 192 | 2 |  |  | 2 |  | 10 | use constant EXIT_PARAM_ERR => 2;   # couldn't understand the command line | 
|  | 2 |  |  |  |  | 6 |  | 
|  | 2 |  |  |  |  | 681 |  | 
| 193 |  |  |  |  |  |  |  | 
| 194 |  |  |  |  |  |  | # }}}1 | 
| 195 |  |  |  |  |  |  | # Globals (ick) {{{1 | 
| 196 |  |  |  |  |  |  |  | 
| 197 |  |  |  |  |  |  | =head2 C<$RUNNING> | 
| 198 |  |  |  |  |  |  |  | 
| 199 |  |  |  |  |  |  | Set truthy when a hopen run is in progress.  This is so modules don't have | 
| 200 |  |  |  |  |  |  | to C<die()> if they are being run under C<perl -c>, for example. | 
| 201 |  |  |  |  |  |  |  | 
| 202 |  |  |  |  |  |  | TODO replace this with a package parameter --- see | 
| 203 |  |  |  |  |  |  | L<App::hopen::HopenFileKit/_language_import>. | 
| 204 |  |  |  |  |  |  |  | 
| 205 |  |  |  |  |  |  | =cut | 
| 206 |  |  |  |  |  |  |  | 
| 207 |  |  |  |  |  |  | our $RUNNING; | 
| 208 |  |  |  |  |  |  |  | 
| 209 |  |  |  |  |  |  | # }}}1 | 
| 210 |  |  |  |  |  |  | # === Command line parsing ============================================== {{{1 | 
| 211 |  |  |  |  |  |  |  | 
| 212 |  |  |  |  |  |  | =head2 %CMDLINE_OPTS | 
| 213 |  |  |  |  |  |  |  | 
| 214 |  |  |  |  |  |  | A hash from internal name to array reference of | 
| 215 |  |  |  |  |  |  | [getopt-name, getopt-options, optional default-value]. | 
| 216 |  |  |  |  |  |  |  | 
| 217 |  |  |  |  |  |  | If default-value is a reference, it will be the destination for that value. | 
| 218 |  |  |  |  |  |  |  | 
| 219 |  |  |  |  |  |  | =cut | 
| 220 |  |  |  |  |  |  |  | 
| 221 |  |  |  |  |  |  | my %CMDLINE_OPTS = ( | 
| 222 |  |  |  |  |  |  | # They are listed in alphabetical order by option name, | 
| 223 |  |  |  |  |  |  | # lowercase before upper, although the code does not require that order. | 
| 224 |  |  |  |  |  |  |  | 
| 225 |  |  |  |  |  |  | ARCHITECTURE => ['a','|A|architecture|platform=s'], | 
| 226 |  |  |  |  |  |  | # -A and --platform are for the comfort of folks migrating from CMake | 
| 227 |  |  |  |  |  |  |  | 
| 228 |  |  |  |  |  |  | BUILD => ['build'],     # If specified, do not | 
| 229 |  |  |  |  |  |  | # run any phases.  Instead, run the | 
| 230 |  |  |  |  |  |  | # build tool indicated by the generator. | 
| 231 |  |  |  |  |  |  |  | 
| 232 |  |  |  |  |  |  | #DUMP_VARS => ['d', '|dump-variables', false], | 
| 233 |  |  |  |  |  |  | #DEBUG => ['debug','', false], | 
| 234 |  |  |  |  |  |  | DEFINE => ['D',':s%'], | 
| 235 |  |  |  |  |  |  | EVAL => ['e','|eval=s@'],   # Perl source to run as a hopen file | 
| 236 |  |  |  |  |  |  | #RESTRICTED_EVAL => ['E','|exec=s@'], | 
| 237 |  |  |  |  |  |  | # TODO add -f to specify additional hopen files | 
| 238 |  |  |  |  |  |  | FRESH => ['fresh'],         # Don't run MY.hopen.pl | 
| 239 |  |  |  |  |  |  | PROJ_DIR => ['from','=s'], | 
| 240 |  |  |  |  |  |  |  | 
| 241 |  |  |  |  |  |  | GENERATOR => ['g', '|G|generator=s', 'Make'],     # -G is from CMake | 
| 242 |  |  |  |  |  |  | # *** This is where the default generator is set *** | 
| 243 |  |  |  |  |  |  | # TODO? add an option to pass parameters to the generator? | 
| 244 |  |  |  |  |  |  | # E.g., which make(1) to use?  Or maybe that should be part of the | 
| 245 |  |  |  |  |  |  | # ARCHITECTURE string. | 
| 246 |  |  |  |  |  |  |  | 
| 247 |  |  |  |  |  |  | #GO => ['go'],  # TODO implement this --- if specified, run all phases | 
| 248 |  |  |  |  |  |  | # and invoke the build tool without requiring the user to | 
| 249 |  |  |  |  |  |  | # re-run hopen. | 
| 250 |  |  |  |  |  |  |  | 
| 251 |  |  |  |  |  |  | # -h and --help reserved | 
| 252 |  |  |  |  |  |  | #INCLUDE => ['i','|include=s@'], | 
| 253 |  |  |  |  |  |  | #LIB => ['l','|load=s@'],   # TODO implement this.  A separate option | 
| 254 |  |  |  |  |  |  | # for libs only used for hopen files? | 
| 255 |  |  |  |  |  |  | #LANGUAGE => ['L','|language:s'], | 
| 256 |  |  |  |  |  |  | # --man reserved | 
| 257 |  |  |  |  |  |  | # OUTPUT_FILENAME => ['o','|output=s', ""], | 
| 258 |  |  |  |  |  |  | # OPTIMIZE => ['O','|optimize'], | 
| 259 |  |  |  |  |  |  |  | 
| 260 |  |  |  |  |  |  | PHASE => ['phase','=s'],    # NO DEFAULT so we can tell if --phase was used | 
| 261 |  |  |  |  |  |  |  | 
| 262 |  |  |  |  |  |  | QUIET => ['q'], | 
| 263 |  |  |  |  |  |  | #SANDBOX => ['S','|sandbox',false], | 
| 264 |  |  |  |  |  |  | #SOURCES reserved | 
| 265 |  |  |  |  |  |  | TOOLSET => ['t','|T|toolset=s'],        # -T is from CMake | 
| 266 |  |  |  |  |  |  | DEST_DIR => ['to','=s'], | 
| 267 |  |  |  |  |  |  | # --usage reserved | 
| 268 |  |  |  |  |  |  | PRINT_VERSION => ['version','', false], | 
| 269 |  |  |  |  |  |  | VERBOSE => ['v','+', 0], | 
| 270 |  |  |  |  |  |  | VERBOSE2 => ['verbose',':s'],   # --verbose=<n> | 
| 271 |  |  |  |  |  |  | # -? reserved | 
| 272 |  |  |  |  |  |  |  | 
| 273 |  |  |  |  |  |  | ); | 
| 274 |  |  |  |  |  |  |  | 
| 275 |  |  |  |  |  |  | sub _parse_command_line { # {{{2 | 
| 276 |  |  |  |  |  |  |  | 
| 277 |  |  |  |  |  |  | =head2 _parse_command_line | 
| 278 |  |  |  |  |  |  |  | 
| 279 |  |  |  |  |  |  | Takes {into=>hash ref, from=>array ref}.  Fills in the hash with the | 
| 280 |  |  |  |  |  |  | values from the command line, keyed by the keys in L</%CMDLINE_OPTS>. | 
| 281 |  |  |  |  |  |  |  | 
| 282 |  |  |  |  |  |  | =cut | 
| 283 |  |  |  |  |  |  |  | 
| 284 | 4 |  |  | 4 |  | 16 | my %params = @_; | 
| 285 |  |  |  |  |  |  | #local @_Sources; | 
| 286 |  |  |  |  |  |  |  | 
| 287 | 4 |  |  |  |  | 11 | my $hrOptsOut = $params{into}; | 
| 288 |  |  |  |  |  |  |  | 
| 289 |  |  |  |  |  |  | # Easier syntax for checking whether optional args were provided. | 
| 290 |  |  |  |  |  |  | # Syntax thanks to http://www.perlmonks.org/?node_id=696592 | 
| 291 | 4 |  |  | 12 |  | 31 | local *have = sub { return exists($hrOptsOut->{ $_[0] }); }; | 
|  | 12 |  |  |  |  | 61 |  | 
| 292 |  |  |  |  |  |  |  | 
| 293 |  |  |  |  |  |  | # Set defaults so we don't have to test them with exists(). | 
| 294 |  |  |  |  |  |  | %$hrOptsOut = (     # map getopt option name to default value | 
| 295 | 12 |  |  |  |  | 43 | map { $CMDLINE_OPTS{ $_ }->[0] => $CMDLINE_OPTS{ $_ }[2] } | 
| 296 | 4 |  |  |  |  | 31 | grep { (scalar @{$CMDLINE_OPTS{ $_ }})==3 } | 
|  | 56 |  |  |  |  | 67 |  | 
|  | 56 |  |  |  |  | 111 |  | 
| 297 |  |  |  |  |  |  | keys %CMDLINE_OPTS | 
| 298 |  |  |  |  |  |  | ); | 
| 299 |  |  |  |  |  |  |  | 
| 300 |  |  |  |  |  |  | # Get options | 
| 301 |  |  |  |  |  |  | my $opts_ok = GetOptionsFromArray( | 
| 302 |  |  |  |  |  |  | $params{from},                  # source array | 
| 303 |  |  |  |  |  |  | $hrOptsOut,                     # destination hash | 
| 304 |  |  |  |  |  |  | 'usage|?', 'h|help', 'man',     # options we handle here | 
| 305 | 4 |  | 66 |  |  | 21 | map { $_->[0] . ($_->[1] // '') } values %CMDLINE_OPTS, # options strs | 
|  | 56 |  |  |  |  | 189 |  | 
| 306 |  |  |  |  |  |  | ); | 
| 307 |  |  |  |  |  |  |  | 
| 308 |  |  |  |  |  |  | # Help, if requested | 
| 309 | 4 | 50 | 33 |  |  | 6428 | if(!$opts_ok || have('usage') || have('h') || have('man')) { | 
|  |  |  | 66 |  |  |  |  | 
|  |  |  | 33 |  |  |  |  | 
| 310 |  |  |  |  |  |  |  | 
| 311 |  |  |  |  |  |  | # Only pull in the Pod routines if we actually need them. | 
| 312 |  |  |  |  |  |  |  | 
| 313 |  |  |  |  |  |  | # Terminal formatting, if present. | 
| 314 |  |  |  |  |  |  | { | 
| 315 | 2 |  |  | 2 |  | 14 | no warnings 'once'; | 
|  | 2 |  |  |  |  | 4 |  | 
|  | 2 |  |  |  |  | 2403 |  | 
|  | 0 |  |  |  |  | 0 |  | 
| 316 | 0 |  |  |  |  | 0 | eval "require Pod::Text::Termcap"; | 
| 317 | 0 | 50 |  |  |  | 0 | $Pod::Usage::Formatter = 'Pod::Text::Termcap' unless $@; | 
| 318 |  |  |  |  |  |  | } | 
| 319 |  |  |  |  |  |  |  | 
| 320 | 0 |  |  |  |  | 0 | require Pod::Usage; | 
| 321 |  |  |  |  |  |  |  | 
| 322 | 0 |  |  |  |  | 0 | my @in = (-input => __FILE__); | 
| 323 | 0 | 50 |  |  |  | 0 | Pod::Usage::pod2usage(-verbose => 0, -exitval => EXIT_PARAM_ERR, @in) | 
| 324 |  |  |  |  |  |  | unless $opts_ok;   # unknown opt | 
| 325 | 0 | 50 |  |  |  | 0 | Pod::Usage::pod2usage(-verbose => 0, -exitval => EXIT_OK, @in) | 
| 326 |  |  |  |  |  |  | if have('usage'); | 
| 327 | 0 | 0 |  |  |  | 0 | Pod::Usage::pod2usage(-verbose => 1, -exitval => EXIT_OK, @in) | 
| 328 |  |  |  |  |  |  | if have('h'); | 
| 329 |  |  |  |  |  |  |  | 
| 330 |  |  |  |  |  |  | # --man: suppress "INTERNALS" section.  Note that this does | 
| 331 |  |  |  |  |  |  | # get rid of the automatic pager we would otherwise get | 
| 332 |  |  |  |  |  |  | # by virtue of pod2usage's invoking perldoc(1).  Oh well. | 
| 333 |  |  |  |  |  |  |  | 
| 334 | 0 | 50 |  |  |  | 0 | Pod::Usage::pod2usage( | 
| 335 |  |  |  |  |  |  | -exitval => EXIT_OK, @in, | 
| 336 |  |  |  |  |  |  | -verbose => 99, -sections => '!INTERNALS'   # suppress INTERNALS | 
| 337 |  |  |  |  |  |  | ) if have('man'); | 
| 338 |  |  |  |  |  |  | } | 
| 339 |  |  |  |  |  |  |  | 
| 340 |  |  |  |  |  |  | # Map the option names from GetOptions back to the internal names we use, | 
| 341 |  |  |  |  |  |  | # e.g., $hrOptsOut->{EVAL} from $hrOptsOut->{e}. | 
| 342 | 4 |  |  |  |  | 20 | my %revmap = map { $CMDLINE_OPTS{$_}->[0] => $_ } keys %CMDLINE_OPTS; | 
|  | 56 |  |  |  |  | 116 |  | 
| 343 | 4 |  |  |  |  | 21 | for my $optname (keys %$hrOptsOut) { | 
| 344 | 22 |  |  |  |  | 58 | $hrOptsOut->{ $revmap{$optname} } = $hrOptsOut->{ $optname }; | 
| 345 |  |  |  |  |  |  | } | 
| 346 |  |  |  |  |  |  |  | 
| 347 |  |  |  |  |  |  | # Process other arguments.  The first two non-option arguments are dest | 
| 348 |  |  |  |  |  |  | # dir and project dir, if --from and --to were not given. | 
| 349 | 4 | 50 | 0 |  |  | 9 | $hrOptsOut->{DEST_DIR} //= $params{from}->[0] if @{$params{from}}; | 
|  | 4 |  |  |  |  | 17 |  | 
| 350 | 4 | 50 | 0 |  |  | 9 | $hrOptsOut->{PROJ_DIR} //= $params{from}->[1] if @{$params{from}}>1; | 
|  | 4 |  |  |  |  | 13 |  | 
| 351 |  |  |  |  |  |  |  | 
| 352 |  |  |  |  |  |  | # Sanity check VERBOSE2, and give it a default of 0 | 
| 353 | 4 |  | 50 |  |  | 22 | my $v2 = $hrOptsOut->{VERBOSE2} // 0; | 
| 354 | 4 | 50 |  |  |  | 15 | $v2 = 1 if $v2 eq '';   # --verbose without value === --verbose=1 | 
| 355 | 4 | 50 | 33 |  |  | 48 | die "--verbose requires a positive numeric argument" | 
|  |  |  | 33 |  |  |  |  | 
| 356 |  |  |  |  |  |  | if (defined $v2) && ( !looks_like_number($v2) || (int($v2) < 0) ); | 
| 357 | 4 |  | 50 |  |  | 40 | $hrOptsOut->{VERBOSE2} = int($v2 // 0); | 
| 358 |  |  |  |  |  |  |  | 
| 359 |  |  |  |  |  |  | } #_parse_command_line() }}}2 | 
| 360 |  |  |  |  |  |  |  | 
| 361 |  |  |  |  |  |  | # }}}1 | 
| 362 |  |  |  |  |  |  | # === Main worker code ================================================== {{{1 | 
| 363 |  |  |  |  |  |  |  | 
| 364 |  |  |  |  |  |  | =head2 $_hrData | 
| 365 |  |  |  |  |  |  |  | 
| 366 |  |  |  |  |  |  | The hashref of the current data we have built up by processing hopen files. | 
| 367 |  |  |  |  |  |  |  | 
| 368 |  |  |  |  |  |  | =cut | 
| 369 |  |  |  |  |  |  |  | 
| 370 |  |  |  |  |  |  | our $_hrData;   # the hashref of current data | 
| 371 |  |  |  |  |  |  |  | 
| 372 |  |  |  |  |  |  | =head2 $_did_set_phase | 
| 373 |  |  |  |  |  |  |  | 
| 374 |  |  |  |  |  |  | Set to truthy if MY.hopen.pl sets the phase. | 
| 375 |  |  |  |  |  |  |  | 
| 376 |  |  |  |  |  |  | =cut | 
| 377 |  |  |  |  |  |  |  | 
| 378 |  |  |  |  |  |  | our $_did_set_phase = false; | 
| 379 |  |  |  |  |  |  | # Whether the current hopen file called set_phase() | 
| 380 |  |  |  |  |  |  |  | 
| 381 |  |  |  |  |  |  | my $_hf_pkg_idx = 0;    # unique ID for the packages of hopen files | 
| 382 |  |  |  |  |  |  |  | 
| 383 |  |  |  |  |  |  | sub _execute_hopen_file {       # Load and run a single hopen file {{{2 | 
| 384 |  |  |  |  |  |  |  | 
| 385 |  |  |  |  |  |  | =head2 _execute_hopen_file | 
| 386 |  |  |  |  |  |  |  | 
| 387 |  |  |  |  |  |  | Execute a single hopen file, but B<do not> run the DAG.  Usage: | 
| 388 |  |  |  |  |  |  |  | 
| 389 |  |  |  |  |  |  | _execute_hopen_file($filename[, options...]) | 
| 390 |  |  |  |  |  |  |  | 
| 391 |  |  |  |  |  |  | This function takes input from L</$_hrData> unless a C<< DATA=>{...} >> option | 
| 392 |  |  |  |  |  |  | is given.  This function updates L</$_hrData> based on the results. | 
| 393 |  |  |  |  |  |  |  | 
| 394 |  |  |  |  |  |  | Options are: | 
| 395 |  |  |  |  |  |  |  | 
| 396 |  |  |  |  |  |  | =over | 
| 397 |  |  |  |  |  |  |  | 
| 398 |  |  |  |  |  |  | =item phase | 
| 399 |  |  |  |  |  |  |  | 
| 400 |  |  |  |  |  |  | If given, force the phase to be the one specified. | 
| 401 |  |  |  |  |  |  |  | 
| 402 |  |  |  |  |  |  | =item quiet | 
| 403 |  |  |  |  |  |  |  | 
| 404 |  |  |  |  |  |  | If truthy, suppress extra output. | 
| 405 |  |  |  |  |  |  |  | 
| 406 |  |  |  |  |  |  | =item libs | 
| 407 |  |  |  |  |  |  |  | 
| 408 |  |  |  |  |  |  | If given, it must be an arrayref of directories.  Each of those will be | 
| 409 |  |  |  |  |  |  | turned into a C<use lib> statement (see L<lib>) in the generated source. | 
| 410 |  |  |  |  |  |  |  | 
| 411 |  |  |  |  |  |  | =back | 
| 412 |  |  |  |  |  |  |  | 
| 413 |  |  |  |  |  |  | =cut | 
| 414 |  |  |  |  |  |  |  | 
| 415 | 6 | 50 |  | 6 |  | 120 | my $fn = shift or croak 'Need a file to run'; | 
| 416 | 6 |  |  |  |  | 24 | my %opts = @_; | 
| 417 | 6 | 50 |  |  |  | 14 | $Phase = $opts{phase} if $opts{phase}; | 
| 418 |  |  |  |  |  |  |  | 
| 419 | 6 |  |  |  |  | 44 | my $merger = Hash::Merge->new('RETAINMENT_PRECEDENT'); | 
| 420 |  |  |  |  |  |  |  | 
| 421 |  |  |  |  |  |  | # == Set up code pieces related to phase control == | 
| 422 |  |  |  |  |  |  |  | 
| 423 | 6 |  |  |  |  | 565 | my ($set_phase, $cannot_set_phase, $cannot_set_phase_warn); | 
| 424 | 6 |  |  |  |  | 13 | my $setting_phase_allowed = false; | 
| 425 |  |  |  |  |  |  |  | 
| 426 |  |  |  |  |  |  | # Note: all phase-setting functions succeed if there was nothing | 
| 427 |  |  |  |  |  |  | # for them to do! | 
| 428 |  |  |  |  |  |  |  | 
| 429 |  |  |  |  |  |  | $set_phase = q( | 
| 430 |  |  |  |  |  |  | sub can_set_phase { true } | 
| 431 |  |  |  |  |  |  | sub set_phase { | 
| 432 |  |  |  |  |  |  | my $new_phase = shift or croak 'Need a phase'; | 
| 433 |  |  |  |  |  |  | return if $App::hopen::BuildSystemGlobals::Phase eq $new_phase; | 
| 434 |  |  |  |  |  |  | croak "Phase $new_phase is not one of the ones I know about (" . | 
| 435 |  |  |  |  |  |  | join(', ', @PHASES) . ')' | 
| 436 |  |  |  |  |  |  | unless defined phase_idx($new_phase); | 
| 437 |  |  |  |  |  |  | $App::hopen::BuildSystemGlobals::Phase = $new_phase; | 
| 438 |  |  |  |  |  |  | $App::hopen::_did_set_phase = true; | 
| 439 |  |  |  |  |  |  | ) . | 
| 440 | 6 | 50 |  |  |  | 27 | ($opts{quiet} ? '' : 'say "Running $new_phase phase";') . "}\n"; | 
| 441 |  |  |  |  |  |  |  | 
| 442 | 6 |  |  |  |  | 15 | $cannot_set_phase = q( | 
| 443 |  |  |  |  |  |  | sub can_set_phase { false } | 
| 444 |  |  |  |  |  |  | sub set_phase { | 
| 445 |  |  |  |  |  |  | my $new_phase = shift // ''; | 
| 446 |  |  |  |  |  |  | return if $App::hopen::BuildSystemGlobals::Phase eq $new_phase; | 
| 447 |  |  |  |  |  |  | croak "I'm sorry, but this file (``$FILENAME'') is not allowed to set the phase" | 
| 448 |  |  |  |  |  |  | } | 
| 449 |  |  |  |  |  |  | ); | 
| 450 |  |  |  |  |  |  |  | 
| 451 |  |  |  |  |  |  | $cannot_set_phase_warn = q( | 
| 452 |  |  |  |  |  |  | sub can_set_phase { false } | 
| 453 |  |  |  |  |  |  | sub set_phase { | 
| 454 |  |  |  |  |  |  | my $new_phase = shift // ''; | 
| 455 |  |  |  |  |  |  | return if $App::hopen::BuildSystemGlobals::Phase eq $new_phase; | 
| 456 |  |  |  |  |  |  | ) . | 
| 457 | 6 | 50 |  |  |  | 30 | ($opts{quiet} ? '' : | 
| 458 |  |  |  |  |  |  | q( | 
| 459 |  |  |  |  |  |  | warn "``$FILENAME'': Ignoring attempt to set phase"; | 
| 460 |  |  |  |  |  |  | ) | 
| 461 |  |  |  |  |  |  | ) . "}\n"; | 
| 462 |  |  |  |  |  |  |  | 
| 463 | 6 |  |  |  |  | 12 | my $lib_dirs = ''; | 
| 464 | 6 | 50 |  |  |  | 17 | if($opts{libs}) { | 
| 465 |  |  |  |  |  |  | $lib_dirs .= "use lib '" .  (dir($_)->absolute =~ s/'/\\'/gr) .  "';\n" | 
| 466 | 0 |  |  |  |  | 0 | foreach @{$opts{libs}}; | 
|  | 0 |  |  |  |  | 0 |  | 
| 467 |  |  |  |  |  |  | } | 
| 468 |  |  |  |  |  |  |  | 
| 469 |  |  |  |  |  |  | # == Make the hopen file into a package we can eval == | 
| 470 |  |  |  |  |  |  |  | 
| 471 | 6 |  |  |  |  | 11 | my ($friendly_name, $pkg_name, $file_text, $phase_text); | 
| 472 |  |  |  |  |  |  |  | 
| 473 | 6 |  |  |  |  | 11 | $phase_text = q( | 
| 474 |  |  |  |  |  |  | use App::hopen::Phases ':all'; | 
| 475 |  |  |  |  |  |  | ); | 
| 476 |  |  |  |  |  |  |  | 
| 477 |  |  |  |  |  |  | # -- Load the file | 
| 478 |  |  |  |  |  |  |  | 
| 479 | 6 | 50 |  |  |  | 15 | if(ref $fn eq 'HASH') {       # it's a -e | 
| 480 | 0 |  |  | 0 |  | 0 | hlog { 'Processing', $fn->{name} }; | 
|  | 0 |  |  |  |  | 0 |  | 
| 481 | 0 |  |  |  |  | 0 | $file_text = $fn->{text}; | 
| 482 | 0 |  |  |  |  | 0 | $friendly_name = $fn->{name}; | 
| 483 | 0 |  |  |  |  | 0 | $pkg_name = 'CmdLineE' . $fn->{num} . '_' . $_hf_pkg_idx++; | 
| 484 | 0 | 0 |  |  |  | 0 | $phase_text .= defined($opts{phase}) ? $cannot_set_phase : $set_phase; | 
| 485 |  |  |  |  |  |  | # -e's can set phase unless --phase was specified | 
| 486 |  |  |  |  |  |  |  | 
| 487 |  |  |  |  |  |  | } else { | 
| 488 | 6 |  |  | 0 |  | 36 | hlog { 'Processing', $fn }; | 
|  | 0 |  |  |  |  | 0 |  | 
| 489 | 6 |  |  |  |  | 60 | $file_text = file($fn)->slurp; | 
| 490 | 6 |  |  |  |  | 2112 | $pkg_name = ($fn =~ s/[^a-zA-Z0-9]/_/gr) . '_' . $_hf_pkg_idx++; | 
| 491 | 6 |  |  |  |  | 144 | $friendly_name = $fn; | 
| 492 |  |  |  |  |  |  |  | 
| 493 | 6 | 100 | 66 |  |  | 24 | if( isMYH($fn) and !defined($opts{phase}) ) { | 
| 494 |  |  |  |  |  |  | # MY.hopen.pl files can set $Phase unless --phase was given. | 
| 495 | 2 |  |  |  |  | 87 | $phase_text .= $set_phase; | 
| 496 | 2 |  |  |  |  | 5 | $setting_phase_allowed = true; | 
| 497 |  |  |  |  |  |  |  | 
| 498 |  |  |  |  |  |  | } else { | 
| 499 |  |  |  |  |  |  | # For MY.hopen.pl, when --phase is set, set_phase doesn't croak. | 
| 500 |  |  |  |  |  |  | # If this were not the case, every second or subsequent run | 
| 501 |  |  |  |  |  |  | # of hopen(1) would croak if --phase were specified! | 
| 502 | 4 | 50 |  |  |  | 71 | $phase_text .= isMYH($fn) ? $cannot_set_phase_warn : $cannot_set_phase; | 
| 503 |  |  |  |  |  |  | # TODO? permit regular hopen files to set the the phase if | 
| 504 |  |  |  |  |  |  | # neither MYH nor the command line did, and we're at the first | 
| 505 |  |  |  |  |  |  | # phase.  This is so the hopen file can say `set_phase 'Gen';` | 
| 506 |  |  |  |  |  |  | # if there's nothing to do during Check. | 
| 507 |  |  |  |  |  |  | } | 
| 508 |  |  |  |  |  |  | } #endif -e else | 
| 509 |  |  |  |  |  |  |  | 
| 510 | 6 |  |  |  |  | 47 | $friendly_name =~ s{"}{-}g; | 
| 511 |  |  |  |  |  |  | # as far as I can tell, #line can't handle embedded quotes. | 
| 512 |  |  |  |  |  |  |  | 
| 513 |  |  |  |  |  |  | # -- Build the package | 
| 514 |  |  |  |  |  |  |  | 
| 515 | 6 |  |  |  |  | 85 | my $src = line_mark_string <<EOT ; | 
| 516 |  |  |  |  |  |  | { | 
| 517 |  |  |  |  |  |  | package __Rpkg_$pkg_name; | 
| 518 | 1 |  |  | 2 |  | 441 | use App::hopen::HopenFileKit "\Q$friendly_name\E"; | 
|  | 1 |  |  | 2 |  | 4 |  | 
|  | 1 |  |  | 1 |  | 7 |  | 
|  | 1 |  |  | 1 |  | 10 |  | 
|  | 1 |  |  | 1 |  | 2 |  | 
|  | 1 |  |  | 1 |  | 7 |  | 
|  | 1 |  |  |  |  | 10 |  | 
|  | 1 |  |  |  |  | 3 |  | 
|  | 1 |  |  |  |  | 6 |  | 
|  | 1 |  |  |  |  | 9 |  | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 10 |  | 
|  | 1 |  |  |  |  | 9 |  | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 11 |  | 
|  | 1 |  |  |  |  | 8 |  | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 8 |  | 
| 519 |  |  |  |  |  |  | # \\Q and \\E since, on Windows, \$friendly_name is likely to | 
| 520 |  |  |  |  |  |  | # include backslashes. | 
| 521 |  |  |  |  |  |  |  | 
| 522 |  |  |  |  |  |  | # Other lib dirs | 
| 523 |  |  |  |  |  |  | $lib_dirs | 
| 524 |  |  |  |  |  |  | # /Other lib dirs | 
| 525 |  |  |  |  |  |  |  | 
| 526 |  |  |  |  |  |  | # Other phase text | 
| 527 |  |  |  |  |  |  | $phase_text | 
| 528 | 1 |  |  | 2 |  | 167 | # /Other phase text | 
|  | 1 |  |  | 1 |  | 3 |  | 
|  | 1 |  |  | 1 |  | 459 |  | 
|  | 1 |  |  | 1 |  | 6 |  | 
|  | 1 |  |  | 1 |  | 2 |  | 
|  | 1 |  |  | 1 |  | 490 |  | 
|  | 1 |  |  |  |  | 6 |  | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 235 |  | 
|  | 1 |  |  |  |  | 6 |  | 
|  | 1 |  |  |  |  | 3 |  | 
|  | 1 |  |  |  |  | 281 |  | 
|  | 1 |  |  |  |  | 7 |  | 
|  | 1 |  |  |  |  | 3 |  | 
|  | 1 |  |  |  |  | 473 |  | 
|  | 1 |  |  |  |  | 7 |  | 
|  | 1 |  |  |  |  | 2 |  | 
|  | 1 |  |  |  |  | 234 |  | 
| 529 |  |  |  |  |  |  | EOT | 
| 530 |  |  |  |  |  |  |  | 
| 531 |  |  |  |  |  |  | # Now shadow $Phase so the hopen file can't change it without | 
| 532 |  |  | 33 |  |  |  | # really trying!  Note that we actually interpolate the current | 
|  |  |  | 33 |  |  |  |  | 
|  |  |  | 0 |  |  |  |  | 
|  |  |  | 33 |  |  |  |  | 
| 533 |  |  |  |  |  |  | # phase in as a literal so that it's read-only (see perlmod). | 
| 534 |  |  |  |  |  |  |  | 
| 535 | 6 | 100 |  |  |  | 18 | unless($setting_phase_allowed) { | 
| 536 | 4 |  |  |  |  | 16 | $src .= line_mark_string <<EOT; | 
| 537 |  |  |  |  |  |  | our \$Phase; | 
| 538 |  |  |  |  |  |  | local *Phase = \\"$Phase"; | 
| 539 |  |  |  |  |  |  | EOT | 
| 540 |  |  |  |  |  |  | } | 
| 541 |  |  |  |  |  |  |  | 
| 542 |  |  |  |  |  |  | # Run the code given in the hopen file.  Wrap it in a named BLOCK so that | 
| 543 |  |  |  |  |  |  | # Phases::on() will work, but don't rely on the return value of that | 
| 544 |  |  |  |  |  |  | # BLOCK (per perlsyn). | 
| 545 |  |  |  |  |  |  |  | 
| 546 | 6 |  |  |  |  | 30 | $src .= line_mark_string <<EOT; | 
| 547 |  |  |  |  |  |  |  | 
| 548 |  |  |  |  |  |  | sub __Rsub_$pkg_name { | 
| 549 |  |  |  |  |  |  | my \$__R_retval; | 
| 550 |  |  |  |  |  |  | __R_DO: { | 
| 551 |  |  |  |  |  |  | \$__R_retval = do {   # return statements in here will exit the Rsub | 
| 552 |  |  |  |  |  |  | #line 1 "$friendly_name" | 
| 553 |  |  |  |  |  |  | $file_text | 
| 554 |  |  |  |  |  |  | }; # do{} | 
| 555 |  |  |  |  |  |  | } #__R_DO | 
| 556 |  |  |  |  |  |  | EOT | 
| 557 |  |  |  |  |  |  |  | 
| 558 |  |  |  |  |  |  | # If the file_text did not expressly return(), control will reach the | 
| 559 |  |  |  |  |  |  | # following block, where we get the correct return value.  If the file_text | 
| 560 |  |  |  |  |  |  | # ran to completion, we have a defined __R_retval.  If the file text exited | 
| 561 |  |  |  |  |  |  | # via Phases::on(), we have a defined __R_on_result.  If either of those | 
| 562 |  |  |  |  |  |  | # is defined, make sure it's not a DAG or GraphBuilder.  Those should not | 
| 563 |  |  |  |  |  |  | # be put into the return data. | 
| 564 |  |  |  |  |  |  | # | 
| 565 |  |  |  |  |  |  | # Also, any defined, non-hash value is ignored, so that we don't | 
| 566 |  |  |  |  |  |  | # wind up with lots of hashes like { 1 => 1 }. | 
| 567 |  |  |  |  |  |  | # | 
| 568 |  |  |  |  |  |  | # If the file_text did expressly return(), whatever it returned will | 
| 569 |  |  |  |  |  |  | # be used as-is.  Like perlref says, we are not totalitarians. | 
| 570 |  |  |  |  |  |  |  | 
| 571 | 6 |  |  |  |  | 38 | $src .= line_mark_string <<EOT; | 
| 572 |  |  | 33 |  |  |  | \$__R_retval //= \$__R_on_result; | 
| 573 |  |  |  |  |  |  |  | 
| 574 |  |  |  |  |  |  | ## hlog { '__Rpkg_$pkg_name retval before checks', | 
| 575 |  |  |  |  |  |  | ##        ref \$__R_retval, Dumper \$__R_retval} 3; | 
| 576 |  |  |  |  |  |  |  | 
| 577 |  |  | 33 |  |  |  | if(defined(\$__R_retval) && ref(\$__R_retval)) { | 
| 578 |  |  |  |  |  |  | die 'Hopen files may not return graphs' | 
| 579 |  |  |  |  |  |  | if eval { \$__R_retval->DOES('Data::Hopen::G::DAG') }; | 
| 580 |  |  |  |  |  |  | die 'Hopen files may not return graph builders (is a ->goal or ->default_goal missing?)' | 
| 581 |  |  |  |  |  |  | if eval { \$__R_retval->DOES('Data::Hopen::G::GraphBuilder') }; | 
| 582 |  |  |  |  |  |  |  | 
| 583 |  |  |  |  |  |  | } | 
| 584 |  |  |  |  |  |  |  | 
| 585 |  |  | 0 |  |  |  | if(defined(\$__R_retval) and ref \$__R_retval ne 'HASH') { | 
| 586 |  |  |  |  |  |  | warn ('Hopen files must return hashrefs; ignoring ' . | 
| 587 |  |  |  |  |  |  | lc(ref(\$__R_retval) || 'scalar')) unless \$QUIET; | 
| 588 |  |  |  |  |  |  | \$__R_retval = undef; | 
| 589 |  |  |  |  |  |  | } | 
| 590 |  |  |  |  |  |  |  | 
| 591 |  |  |  |  |  |  | return \$__R_retval; | 
| 592 |  |  |  |  |  |  | } #__Rsub_$pkg_name | 
| 593 |  |  |  |  |  |  |  | 
| 594 |  |  |  |  |  |  | our \$hrNewData = __Rsub_$pkg_name(\$App::hopen::_hrData); | 
| 595 |  |  |  |  |  |  | } #package | 
| 596 |  |  |  |  |  |  | EOT | 
| 597 |  |  |  |  |  |  | # Put the result in a package variable because that way I don't have | 
| 598 |  |  |  |  |  |  | # to remember the rules for the return value of eval(). | 
| 599 |  |  |  |  |  |  |  | 
| 600 | 6 |  |  | 0 |  | 42 | hlog { "Source for $fn\n", $src, "\n" } 3; | 
|  | 0 |  |  |  |  | 0 |  | 
| 601 |  |  |  |  |  |  |  | 
| 602 |  |  |  |  |  |  | # == Run the package == | 
| 603 |  |  |  |  |  |  |  | 
| 604 | 6 |  |  |  |  | 560 | eval($src); | 
| 605 | 6 | 50 |  |  |  | 38 | die "Error in $friendly_name: $@" if $@; | 
| 606 |  |  |  |  |  |  |  | 
| 607 |  |  |  |  |  |  | # Get the data from the package we just ran | 
| 608 | 6 |  |  |  |  | 11 | my $hrAddlData = eval { | 
| 609 | 2 |  |  | 2 |  | 16 | no strict 'refs'; | 
|  | 2 |  |  |  |  | 5 |  | 
|  | 2 |  |  |  |  | 5033 |  | 
| 610 | 6 |  |  |  |  | 9 | ${ "__Rpkg_$pkg_name\::hrNewData" } | 
|  | 6 |  |  |  |  | 24 |  | 
| 611 |  |  |  |  |  |  | }; | 
| 612 |  |  |  |  |  |  |  | 
| 613 | 6 |  |  | 0 |  | 46 | hlog { 'old data', Dumper($_hrData) } 3; | 
|  | 0 |  |  |  |  | 0 |  | 
| 614 | 6 |  |  | 0 |  | 63 | hlog { 'new data', Dumper($hrAddlData) } 2; | 
|  | 0 |  |  |  |  | 0 |  | 
| 615 |  |  |  |  |  |  |  | 
| 616 |  |  |  |  |  |  | # TODO? Remove all __R* hash keys from $hrAddlData unless it's a | 
| 617 |  |  |  |  |  |  | # MY.hopen.pl file? | 
| 618 |  |  |  |  |  |  |  | 
| 619 |  |  |  |  |  |  | # == Merge in the data == | 
| 620 |  |  |  |  |  |  |  | 
| 621 | 6 | 50 |  |  |  | 62 | $_hrData = $merger->merge($_hrData, $hrAddlData) if $hrAddlData; | 
| 622 | 6 |  |  | 0 |  | 848 | hlog { 'data after merge', Dumper($_hrData) } 2; | 
|  | 0 |  |  |  |  | 0 |  | 
| 623 |  |  |  |  |  |  |  | 
| 624 |  |  |  |  |  |  | } #_execute_hopen_file() }}}2 | 
| 625 |  |  |  |  |  |  |  | 
| 626 |  |  |  |  |  |  | sub _run_phase {    # Run a single phase. {{{2 | 
| 627 |  |  |  |  |  |  |  | 
| 628 |  |  |  |  |  |  | =head2 _run_phase | 
| 629 |  |  |  |  |  |  |  | 
| 630 |  |  |  |  |  |  | Run a phase by executing the hopen files and running the DAG. | 
| 631 |  |  |  |  |  |  | Reads from and writes to L</$_hrData>, which must be initialized by | 
| 632 |  |  |  |  |  |  | the caller.  Usage: | 
| 633 |  |  |  |  |  |  |  | 
| 634 |  |  |  |  |  |  | my $hrDagOutput = _run_phase(files=>[...][, options...]) | 
| 635 |  |  |  |  |  |  |  | 
| 636 |  |  |  |  |  |  | Options C<phase>, C<quiet>, and C<libs> are as L</_execute_hopen_file>. | 
| 637 |  |  |  |  |  |  | Other options are: | 
| 638 |  |  |  |  |  |  |  | 
| 639 |  |  |  |  |  |  | =over | 
| 640 |  |  |  |  |  |  |  | 
| 641 |  |  |  |  |  |  | =item files | 
| 642 |  |  |  |  |  |  |  | 
| 643 |  |  |  |  |  |  | (Required) An arrayref of filenames to run | 
| 644 |  |  |  |  |  |  |  | 
| 645 |  |  |  |  |  |  | =item norun | 
| 646 |  |  |  |  |  |  |  | 
| 647 |  |  |  |  |  |  | (Optional) if truthy, do not run the DAG.  Note that the DAG will also not | 
| 648 |  |  |  |  |  |  | be run if it is empty. | 
| 649 |  |  |  |  |  |  |  | 
| 650 |  |  |  |  |  |  | =back | 
| 651 |  |  |  |  |  |  |  | 
| 652 |  |  |  |  |  |  | =cut | 
| 653 |  |  |  |  |  |  |  | 
| 654 | 4 |  |  | 4 |  | 94 | my %opts = @_; | 
| 655 | 4 | 50 |  |  |  | 13 | $Phase = $opts{phase} if $opts{phase}; | 
| 656 | 4 |  |  |  |  | 11 | my $lrHopenFiles = $opts{files}; | 
| 657 | 4 | 50 |  |  |  | 13 | croak 'Need files=>[...]' unless ref $lrHopenFiles eq 'ARRAY'; | 
| 658 | 4 |  |  | 0 |  | 21 | hlog { Phase => $Phase, Running => Dumper($lrHopenFiles) }; | 
|  | 0 |  |  |  |  | 0 |  | 
| 659 |  |  |  |  |  |  |  | 
| 660 |  |  |  |  |  |  | # = Process the files ====================================== | 
| 661 |  |  |  |  |  |  |  | 
| 662 | 4 |  |  |  |  | 29 | foreach my $fn (@$lrHopenFiles) { | 
| 663 | 4 |  |  |  |  | 11 | _execute_hopen_file($fn, | 
| 664 |  |  |  |  |  |  | forward_opts(\%opts, qw(phase quiet libs)) | 
| 665 |  |  |  |  |  |  | ); | 
| 666 |  |  |  |  |  |  | } # foreach hopen file | 
| 667 |  |  |  |  |  |  |  | 
| 668 | 1 | 50 |  | 0 |  | 5 | hlog { 'Graph is', ($Build->empty ? 'empty.' : 'not empty.'), | 
| 669 | 4 |  |  |  |  | 52 | ' Final data is', Dumper($_hrData) } 2; | 
| 670 |  |  |  |  |  |  |  | 
| 671 | 5 |  |  | 1 |  | 40 | hlog { 'Build graph', '' . $Build->_graph } 5; | 
|  | 1 |  |  |  |  | 4 |  | 
| 672 | 5 |  |  | 0 |  | 36 | hlog { Data::Dumper->new([$Build], ['$Build'])->Indent(1)->Dump } 9; | 
|  | 1 |  |  |  |  | 2 |  | 
| 673 |  |  |  |  |  |  |  | 
| 674 |  |  |  |  |  |  | # If there is no build graph, just return the data.  This is useful | 
| 675 |  |  |  |  |  |  | # enough for debugging that I am making it documented behaviour. | 
| 676 |  |  |  |  |  |  |  | 
| 677 | 5 | 100 | 66 |  |  | 61 | return $_hrData if $Build->empty or $opts{norun}; | 
| 678 |  |  |  |  |  |  |  | 
| 679 |  |  |  |  |  |  | # = Execute the resulting build graph ====================== | 
| 680 |  |  |  |  |  |  |  | 
| 681 |  |  |  |  |  |  | # Wrap the final data in a Scope | 
| 682 | 2 |  |  |  |  | 371 | my $env = Data::Hopen::Scope::Environment->new(name => 'outermost'); | 
| 683 | 2 |  |  |  |  | 161 | my $scope = Data::Hopen::Scope::Hash->new(name => 'from hopen files'); | 
| 684 | 2 |  |  |  |  | 139 | $scope->adopt_hash($_hrData); | 
| 685 | 2 |  |  |  |  | 116 | $scope->outer($env);    # make the environment accessible... | 
| 686 | 2 |  |  |  |  | 45 | $scope->local(true);    # ... but not copied by local-scope calls. | 
| 687 |  |  |  |  |  |  |  | 
| 688 |  |  |  |  |  |  | # Run the DAG | 
| 689 | 2 |  |  |  |  | 27 | my $result_data = $Build->run(-context => $scope, -phase => $Phase, | 
| 690 |  |  |  |  |  |  | -visitor => $Generator); | 
| 691 | 3 |  |  | 0 |  | 3541 | hlog { Data::Dumper->new([$result_data], ['Build graph result data'])->Indent(1)->Dump } 2; | 
|  | 1 |  |  |  |  | 4 |  | 
| 692 | 3 |  |  |  |  | 20 | return $result_data; | 
| 693 |  |  |  |  |  |  | } #_run_phase() }}}2 | 
| 694 |  |  |  |  |  |  |  | 
| 695 |  |  |  |  |  |  | sub _inner {    # Run a single invocation of hopen(1). {{{2 | 
| 696 |  |  |  |  |  |  |  | 
| 697 |  |  |  |  |  |  | =head2 _inner | 
| 698 |  |  |  |  |  |  |  | 
| 699 |  |  |  |  |  |  | Do the work for one invocation of hopen(1).  Dies on failure.  Main() then | 
| 700 |  |  |  |  |  |  | translates the die() into a print and error return. | 
| 701 |  |  |  |  |  |  |  | 
| 702 |  |  |  |  |  |  | Takes a hash of options. | 
| 703 |  |  |  |  |  |  |  | 
| 704 |  |  |  |  |  |  | The return value of _inner is unspecified and ignored. | 
| 705 |  |  |  |  |  |  |  | 
| 706 |  |  |  |  |  |  | =cut | 
| 707 |  |  |  |  |  |  |  | 
| 708 | 5 |  |  | 5 |  | 23 | my %opts = @_; | 
| 709 | 5 |  |  |  |  | 13 | local $_hrData = {}; | 
| 710 |  |  |  |  |  |  |  | 
| 711 |  |  |  |  |  |  | # TODO FIXME.  This is a bit of a hack: Reset global variables on --fresh. | 
| 712 |  |  |  |  |  |  | # Instead, App::hopen should be a class, and each instance should have its | 
| 713 |  |  |  |  |  |  | # own data (I think). | 
| 714 | 5 | 100 |  |  |  | 36 | if($opts{FRESH}) { | 
| 715 | 2 |  |  |  |  | 3 | $_did_set_phase = false; | 
| 716 |  |  |  |  |  |  | } | 
| 717 |  |  |  |  |  |  |  | 
| 718 | 4 | 50 |  |  |  | 13 | if($opts{PRINT_VERSION}) {  # print version, raw and dotted | 
| 719 | 0 | 0 |  |  |  | 0 | if($App::hopen::VERSION =~ m<^([^\.]+)\.(\d{3})(\d{3})>) { | 
| 720 | 1 |  |  |  |  | 2 | printf "hopen version %d.%d.%d ($App::hopen::VERSION)\n", $1, $2, $3; | 
| 721 |  |  |  |  |  |  | } else { | 
| 722 | 1 |  |  |  |  | 2 | say "hopen $VERSION"; | 
| 723 |  |  |  |  |  |  | } | 
| 724 | 0 | 50 |  |  |  | 0 | say "App::hopen in: $INC{'App/hopen.pm'}" if $VERBOSE >= 1; | 
| 725 | 0 |  |  |  |  | 0 | return; | 
| 726 |  |  |  |  |  |  | } | 
| 727 |  |  |  |  |  |  |  | 
| 728 |  |  |  |  |  |  | # = Initialize filesystem-related build-system globals ================== | 
| 729 |  |  |  |  |  |  |  | 
| 730 |  |  |  |  |  |  | # Start with the default phase unless one was specified. | 
| 731 | 4 |  | 33 |  |  | 29 | $Phase = $opts{PHASE} // $PHASES[0]; | 
| 732 | 5 | 50 |  |  |  | 24 | die "Phase $Phase is not one of the ones I know about (" . | 
| 733 |  |  |  |  |  |  | join(', ', @PHASES) . ')' | 
| 734 |  |  |  |  |  |  | unless defined phase_idx($Phase); | 
| 735 |  |  |  |  |  |  |  | 
| 736 |  |  |  |  |  |  | # Get the project dir | 
| 737 | 5 | 50 |  |  |  | 29 | my $proj_dir = $opts{PROJ_DIR} ? dir($opts{PROJ_DIR}) : dir;    #default=cwd | 
| 738 | 5 |  |  |  |  | 222 | $ProjDir = $proj_dir; | 
| 739 |  |  |  |  |  |  |  | 
| 740 |  |  |  |  |  |  | # Get the destination dir | 
| 741 | 5 |  |  |  |  | 99 | my $dest_dir; | 
| 742 | 5 | 100 |  |  |  | 763 | if($opts{DEST_DIR}) { | 
| 743 | 5 |  |  |  |  | 22 | $dest_dir = dir($opts{DEST_DIR}); | 
| 744 |  |  |  |  |  |  | } else { | 
| 745 | 1 |  |  |  |  | 576 | $dest_dir = $proj_dir->subdir('built'); | 
| 746 |  |  |  |  |  |  | } | 
| 747 | 4 |  |  |  |  | 167 | $DestDir = $dest_dir; | 
| 748 |  |  |  |  |  |  |  | 
| 749 |  |  |  |  |  |  | # Prohibit in-source builds | 
| 750 | 4 | 50 |  |  |  | 28 | die <<EOT if $proj_dir eq $dest_dir; | 
| 751 |  |  |  |  |  |  | I'm sorry, but I don't support in-source builds (dir ``$proj_dir'').  Please | 
| 752 |  |  |  |  |  |  | specify a different project directory (--from) or destination directory (--to). | 
| 753 |  |  |  |  |  |  | EOT | 
| 754 |  |  |  |  |  |  |  | 
| 755 |  |  |  |  |  |  | # Prohibit builds if there's a MY.hopen.pl file in the project directory, | 
| 756 |  |  |  |  |  |  | # since those are the marker of a destination directory. | 
| 757 | 5 | 100 |  |  |  | 163 | if(-e $proj_dir->file(MYH)) { die <<EOT; } | 
|  | 1 |  |  |  |  | 3 |  | 
| 758 |  |  |  |  |  |  | I'm sorry, but project directory ``$proj_dir'' appears to actually be a | 
| 759 | 1 |  |  |  |  | 3 | build directory --- it has a @{[MYH]} file.  If you really want to build | 
| 760 | 0 |  |  |  |  | 0 | here, remove or rename @{[MYH]} and run me again. | 
| 761 |  |  |  |  |  |  | EOT | 
| 762 |  |  |  |  |  |  |  | 
| 763 |  |  |  |  |  |  | # See if we have hopen files associated with the project dir | 
| 764 | 4 |  |  |  |  | 911 | my $myhopen = find_myhopen($dest_dir, !!$opts{FRESH}); | 
| 765 | 4 |  |  |  |  | 117 | my $lrHopenFiles = find_hopen_files($proj_dir, $dest_dir, !!$opts{FRESH}); | 
| 766 |  |  |  |  |  |  |  | 
| 767 |  |  |  |  |  |  | # Check the mtimes - we don't use MYH if another hopen file is newer. | 
| 768 | 5 | 100 | 66 |  |  | 6275 | if($myhopen && -e $myhopen) { | 
| 769 | 3 |  |  |  |  | 131 | my $myhstat = file($myhopen)->stat; | 
| 770 |  |  |  |  |  |  |  | 
| 771 | 3 |  |  |  |  | 701 | foreach my $fn (@$lrHopenFiles) { | 
| 772 | 3 |  |  |  |  | 19 | my $stat = file($fn)->stat; | 
| 773 |  |  |  |  |  |  |  | 
| 774 | 3 | 50 | 33 |  |  | 1217 | if( $stat->mtime > $myhstat->mtime || | 
| 775 |  |  |  |  |  |  | $stat->ctime > $myhstat->ctime) | 
| 776 |  |  |  |  |  |  | { | 
| 777 | 1 | 0 |  |  |  | 10 | say "Skipping out-of-date ``$myhopen''" unless $QUIET; | 
| 778 | 1 |  |  |  |  | 647 | $myhopen = undef; | 
| 779 | 0 |  |  |  |  | 0 | last; | 
| 780 |  |  |  |  |  |  | } | 
| 781 |  |  |  |  |  |  | } #foreach hopen file | 
| 782 |  |  |  |  |  |  | } #if MYH exists | 
| 783 |  |  |  |  |  |  |  | 
| 784 |  |  |  |  |  |  | # Add -e's to the list of hopen files | 
| 785 | 4 | 50 |  |  |  | 160 | if($opts{EVAL}) { | 
| 786 | 1 |  |  |  |  | 11 | my $which_e = 0; | 
| 787 |  |  |  |  |  |  | push @$lrHopenFiles, | 
| 788 |  |  |  |  |  |  | map { | 
| 789 | 1 |  |  |  |  | 3 | ++$which_e; | 
| 790 | 0 |  |  |  |  | 0 | +{text=>$_, num=>$which_e, name=>("-e #" . $which_e)} | 
| 791 | 1 |  |  |  |  | 3 | } @{$opts{EVAL}}; | 
|  | 0 |  |  |  |  | 0 |  | 
| 792 |  |  |  |  |  |  | } | 
| 793 |  |  |  |  |  |  |  | 
| 794 |  |  |  |  |  |  | hlog { 'hopen files: ', | 
| 795 | 1 | 50 | 33 | 0 |  | 8 | map { ref eq 'HASH' ? "<<$_->{text}>>" : "``$_''" } | 
|  | 1 |  |  |  |  | 6 |  | 
| 796 | 4 |  |  |  |  | 29 | ($myhopen // (), @$lrHopenFiles) } 2; | 
| 797 |  |  |  |  |  |  |  | 
| 798 |  |  |  |  |  |  | # Can't proceed if the only hopen file is $myhopen. | 
| 799 | 5 | 50 |  |  |  | 46 | unless(@$lrHopenFiles) { die <<EOT; } | 
|  | 1 |  |  |  |  | 28 |  | 
| 800 |  |  |  |  |  |  | I can't find any hopen project files (.hopen.pl or *.hopen.pl) for | 
| 801 |  |  |  |  |  |  | project directory ``$proj_dir''. | 
| 802 |  |  |  |  |  |  | EOT | 
| 803 |  |  |  |  |  |  |  | 
| 804 |  |  |  |  |  |  | # Prepare the destination directory if it doesn't exist | 
| 805 | 5 | 50 |  |  |  | 717 | File::Path::Tiny::mk($dest_dir) or die "Couldn't create $dest_dir: $!"; | 
| 806 |  |  |  |  |  |  |  | 
| 807 |  |  |  |  |  |  | # Create the initial DAG before loading anything so that the | 
| 808 |  |  |  |  |  |  | # generator and toolset can add initialization operations. | 
| 809 | 5 |  |  |  |  | 198 | $Build = hnew DAG => '__R_main'; | 
| 810 |  |  |  |  |  |  |  | 
| 811 |  |  |  |  |  |  | # = Load generator and toolset (and run MYH) ============================ | 
| 812 |  |  |  |  |  |  |  | 
| 813 | 5 | 100 |  |  |  | 131152 | say "From ``$proj_dir'' into ``$dest_dir''" unless $QUIET; | 
| 814 |  |  |  |  |  |  |  | 
| 815 |  |  |  |  |  |  | # Load MY.hopen.pl first so the results of the Probe phase are | 
| 816 |  |  |  |  |  |  | # available to the generator and toolset. | 
| 817 | 4 | 100 | 66 |  |  | 352 | if($myhopen && !$opts{BUILD}) { | 
| 818 | 2 |  |  |  |  | 39 | _execute_hopen_file($myhopen, | 
| 819 |  |  |  |  |  |  | forward_opts(\%opts, {lc=>1}, qw(PHASE QUIET)), | 
| 820 |  |  |  |  |  |  | );  # TODO support _e_h_f libs option | 
| 821 |  |  |  |  |  |  | } | 
| 822 |  |  |  |  |  |  |  | 
| 823 |  |  |  |  |  |  | # Tell the user the initial phase if MY.hopen.pl didn't change it | 
| 824 | 5 | 50 | 66 |  |  | 91 | say "Running $Phase phase" unless $opts{BUILD} or $_did_set_phase or $QUIET; | 
|  |  |  | 66 |  |  |  |  | 
| 825 |  |  |  |  |  |  |  | 
| 826 |  |  |  |  |  |  | # Load generator | 
| 827 |  |  |  |  |  |  | { | 
| 828 | 5 |  |  |  |  | 13 | my ($gen, $gen_class); | 
| 829 | 4 |  |  |  |  | 22 | $gen_class = loadfrom($opts{GENERATOR}, 'App::hopen::Gen::', ''); | 
| 830 | 4 | 50 |  |  |  | 214 | die "Can't find generator $opts{GENERATOR}" unless $gen_class; | 
| 831 | 4 |  |  | 1 |  | 35 | hlog { "Generator spec ``$opts{GENERATOR}'' -> using generator $gen_class" }; | 
|  | 1 |  |  |  |  | 6091 |  | 
| 832 |  |  |  |  |  |  |  | 
| 833 |  |  |  |  |  |  | $gen = "$gen_class"->new(proj_dir => $proj_dir, dest_dir => $dest_dir, | 
| 834 |  |  |  |  |  |  | architecture => $opts{ARCHITECTURE}) | 
| 835 | 5 | 50 |  |  |  | 96 | or die "Can't initialize generator"; | 
| 836 | 5 |  |  |  |  | 132 | $Generator = $gen; | 
| 837 |  |  |  |  |  |  | } | 
| 838 |  |  |  |  |  |  |  | 
| 839 |  |  |  |  |  |  | # Load toolset | 
| 840 |  |  |  |  |  |  | { | 
| 841 | 5 |  |  |  |  | 14 | my $toolset_class; | 
|  | 5 |  |  |  |  | 250 |  | 
|  | 5 |  |  |  |  | 747 |  | 
| 842 | 5 |  | 33 |  |  | 42 | $opts{TOOLSET} //= $Generator->default_toolset; | 
| 843 |  |  |  |  |  |  | $toolset_class = loadfrom($opts{TOOLSET}, | 
| 844 | 5 |  |  |  |  | 586 | 'App::hopen::T::', ''); | 
| 845 | 4 | 50 |  |  |  | 286 | die "Can't find toolset $opts{TOOLSET}" unless $toolset_class; | 
| 846 |  |  |  |  |  |  |  | 
| 847 | 4 |  |  | 1 |  | 34 | hlog { "Toolset spec ``$opts{TOOLSET}'' -> using toolset $toolset_class" }; | 
|  | 1 |  |  |  |  | 15 |  | 
| 848 | 5 |  |  |  |  | 39 | $Toolset = $toolset_class; | 
| 849 |  |  |  |  |  |  | } | 
| 850 |  |  |  |  |  |  |  | 
| 851 |  |  |  |  |  |  | # Handle --build, now that everything's loaded -------------- | 
| 852 | 5 | 50 |  |  |  | 272 | if($opts{BUILD}) { | 
| 853 | 1 |  |  |  |  | 3 | $Generator->run_build(); | 
| 854 | 1 |  |  |  |  | 288 | return; | 
| 855 |  |  |  |  |  |  | } | 
| 856 |  |  |  |  |  |  |  | 
| 857 |  |  |  |  |  |  | # = Run the hopen files (except MYH, already run) ======================= | 
| 858 |  |  |  |  |  |  |  | 
| 859 | 4 |  |  |  |  | 8 | my $new_data; | 
| 860 | 4 | 50 |  |  |  | 14 | if(@$lrHopenFiles) { | 
| 861 | 4 |  |  |  |  | 32 | $new_data = _run_phase( | 
| 862 |  |  |  |  |  |  | files => [@$lrHopenFiles], | 
| 863 |  |  |  |  |  |  | forward_opts(\%opts, {lc=>1}, qw(PHASE QUIET)) | 
| 864 |  |  |  |  |  |  | );      # TODO support _run_phase libs option | 
| 865 |  |  |  |  |  |  |  | 
| 866 |  |  |  |  |  |  | } else {    # No hopen files (other than MYH) => just use the data from MYH | 
| 867 | 0 |  |  |  |  | 0 | $new_data = $_hrData; | 
| 868 |  |  |  |  |  |  | } | 
| 869 |  |  |  |  |  |  |  | 
| 870 | 4 |  |  |  |  | 340 | $Generator->finalize(-phase => $Phase, -dag => $Build, | 
| 871 |  |  |  |  |  |  | -data => $new_data); | 
| 872 |  |  |  |  |  |  | # TODO RESUME HERE - figure out how the generator works into this. | 
| 873 |  |  |  |  |  |  |  | 
| 874 |  |  |  |  |  |  | # = Save state in MY.hopen.pl for the next run ========================== | 
| 875 |  |  |  |  |  |  |  | 
| 876 |  |  |  |  |  |  | # If we get here, _run_phase succeeded.  Therefore, we can move | 
| 877 |  |  |  |  |  |  | # on to the next phase. | 
| 878 | 4 |  | 100 |  |  | 232 | my $new_phase = next_phase($Phase) // $Phase; | 
| 879 |  |  |  |  |  |  |  | 
| 880 |  |  |  |  |  |  | # TODO? give the generators a way to stash information that will be | 
| 881 |  |  |  |  |  |  | # written at the top of MY.hopen.pl.  This way, the user may only | 
| 882 |  |  |  |  |  |  | # need to edit right at the top of the file, and not also throughout | 
| 883 |  |  |  |  |  |  | # the hashref. | 
| 884 |  |  |  |  |  |  |  | 
| 885 | 4 |  |  |  |  | 12 | my $VAR = '__R_new_data'; | 
| 886 | 4 |  |  |  |  | 35 | my $dumper = Data::Dumper->new([$new_data], [$VAR]); | 
| 887 | 4 |  |  |  |  | 189 | $dumper->Pad(' ' x 12);     # To line up with the do{} | 
| 888 | 4 |  |  |  |  | 46 | $dumper->Indent(1);         # fixed indent size | 
| 889 | 4 |  |  |  |  | 65 | $dumper->Quotekeys(0); | 
| 890 | 4 |  |  |  |  | 32 | $dumper->Purity(1); | 
| 891 | 4 |  |  |  |  | 33 | $dumper->Maxrecurse(0);     # no limit | 
| 892 | 4 |  |  |  |  | 28 | $dumper->Sortkeys(true);    # For consistency between runs | 
| 893 | 4 |  |  |  |  | 34 | $dumper->Sparseseen(true);  # We don't use Seen() | 
| 894 |  |  |  |  |  |  |  | 
| 895 |  |  |  |  |  |  | # Four-space indent instead of two-space.  This is using an undocumented | 
| 896 |  |  |  |  |  |  | # feature of Data::Dumper, whence the eval{}. | 
| 897 | 4 |  |  |  |  | 24 | eval { $dumper->{xpad} = ' ' x 4 }; | 
|  | 4 |  |  |  |  | 11 |  | 
| 898 |  |  |  |  |  |  |  | 
| 899 | 4 |  |  |  |  | 11 | my $new_text = dedent [], qq( | 
| 900 | 4 |  |  |  |  | 19 | # @{[MYH]} generated at @{[scalar gmtime]} GMT | 
|  | 4 |  |  |  |  | 46 |  | 
| 901 | 4 |  |  |  |  | 22 | # From ``@{[$proj_dir->absolute]}'' into ``@{[$dest_dir->absolute]}'' | 
|  | 4 |  |  |  |  | 609 |  | 
| 902 |  |  |  |  |  |  |  | 
| 903 |  |  |  |  |  |  | set_phase '$new_phase'; | 
| 904 |  |  |  |  |  |  | do { | 
| 905 |  |  |  |  |  |  | my \$$VAR; | 
| 906 | 4 |  |  |  |  | 532 | @{[$dumper->Dump]} | 
| 907 |  |  |  |  |  |  | \$$VAR | 
| 908 |  |  |  |  |  |  | } | 
| 909 |  |  |  |  |  |  | ); | 
| 910 |  |  |  |  |  |  |  | 
| 911 |  |  |  |  |  |  | # Notes on the above $new_text: | 
| 912 |  |  |  |  |  |  | # - No semi after the Dump line --- Dump adds it automatically. | 
| 913 |  |  |  |  |  |  | # - Dump() may produce multiple statements, so add the | 
| 914 |  |  |  |  |  |  | #   express $__R_new_data at the end so the do{} will have a | 
| 915 |  |  |  |  |  |  | #   consistent return value. | 
| 916 |  |  |  |  |  |  | # - The Dump() line is not indented because it does its own indentation. | 
| 917 |  |  |  |  |  |  |  | 
| 918 | 4 |  |  |  |  | 824 | $dest_dir->file(MYH)->spew($new_text); | 
| 919 |  |  |  |  |  |  |  | 
| 920 |  |  |  |  |  |  | } #_inner() }}}2 | 
| 921 |  |  |  |  |  |  |  | 
| 922 |  |  |  |  |  |  | # }}}1 | 
| 923 |  |  |  |  |  |  | # === Command-line runner =============================================== {{{1 | 
| 924 |  |  |  |  |  |  |  | 
| 925 |  |  |  |  |  |  | sub Main { | 
| 926 |  |  |  |  |  |  |  | 
| 927 |  |  |  |  |  |  | =head2 Main | 
| 928 |  |  |  |  |  |  |  | 
| 929 |  |  |  |  |  |  | Command-line runner.  Call as C<< App::hopen::Main(\@ARGV) >>. | 
| 930 |  |  |  |  |  |  |  | 
| 931 |  |  |  |  |  |  | =cut | 
| 932 |  |  |  |  |  |  |  | 
| 933 | 4 |  | 66 | 5 | 1 | 17598 | my $lrArgs = shift // []; | 
| 934 |  |  |  |  |  |  |  | 
| 935 |  |  |  |  |  |  | # = Process options ===================================================== | 
| 936 |  |  |  |  |  |  |  | 
| 937 | 4 |  |  |  |  | 10 | my %opts; | 
| 938 | 4 |  |  |  |  | 22 | _parse_command_line(from => $lrArgs, into => \%opts); | 
| 939 |  |  |  |  |  |  |  | 
| 940 |  |  |  |  |  |  | # Verbosity is the max of -v and --verbose | 
| 941 | 4 | 50 |  |  |  | 16 | $opts{VERBOSE} = $opts{VERBOSE2} if $opts{VERBOSE2} > $opts{VERBOSE}; | 
| 942 |  |  |  |  |  |  |  | 
| 943 |  |  |  |  |  |  | # Option overrides: -q beats -v | 
| 944 | 4 | 50 |  |  |  | 11 | $opts{VERBOSE} = 0 if $opts{QUIET}; | 
| 945 | 4 |  | 50 |  |  | 23 | $QUIET = !!($opts{QUIET} // false); | 
| 946 | 4 |  |  |  |  | 9 | delete $opts{QUIET}; | 
| 947 |  |  |  |  |  |  | # After this, code only refers to $QUIET for consistency. | 
| 948 |  |  |  |  |  |  |  | 
| 949 |  |  |  |  |  |  | # Implement verbosity | 
| 950 | 4 | 50 | 33 |  |  | 25 | if(!$QUIET && $opts{VERBOSE}) { | 
| 951 | 0 |  |  |  |  | 0 | $VERBOSE += $opts{VERBOSE}; | 
| 952 |  |  |  |  |  |  | #hlog { Verbosity => $VERBOSE }; | 
| 953 |  |  |  |  |  |  |  | 
| 954 |  |  |  |  |  |  | # Under -v, keep stdout and stderr lines in order. | 
| 955 | 0 |  |  |  |  | 0 | STDOUT->autoflush(true); | 
| 956 | 0 |  |  |  |  | 0 | STDERR->autoflush(true); | 
| 957 |  |  |  |  |  |  | } | 
| 958 |  |  |  |  |  |  |  | 
| 959 | 4 |  |  |  |  | 16 | delete @opts{qw(VERBOSE VERBOSE2)}; | 
| 960 |  |  |  |  |  |  | # After this, code only refers to $QUIET for consistency. | 
| 961 |  |  |  |  |  |  | # After th | 
| 962 |  |  |  |  |  |  |  | 
| 963 |  |  |  |  |  |  | # Don't print the source of an eval'ed hopen file unless -vvv or higher. | 
| 964 |  |  |  |  |  |  | # Need 3 for the "..." that Carp prints when truncating. | 
| 965 | 4 | 50 |  |  |  | 15 | $Carp::MaxEvalLen = 3 unless $VERBOSE >= 3; | 
| 966 |  |  |  |  |  |  |  | 
| 967 |  |  |  |  |  |  | # = Do it, Rockapella! ================================================== | 
| 968 |  |  |  |  |  |  |  | 
| 969 | 4 |  |  |  |  | 9 | $RUNNING = true; | 
| 970 | 4 |  |  |  |  | 8 | eval { _inner(%opts); }; | 
|  | 4 |  |  |  |  | 18 |  | 
| 971 | 4 |  |  |  |  | 1795 | my $msg = $@; | 
| 972 | 4 |  |  |  |  | 10 | $RUNNING = false; | 
| 973 |  |  |  |  |  |  |  | 
| 974 | 4 | 50 |  |  |  | 16 | if($msg) { | 
| 975 | 0 |  |  |  |  | 0 | print STDERR $msg; | 
| 976 | 0 |  |  |  |  | 0 | return EXIT_PROC_ERR;   # eval{} so we can do this (die() exitcode = 2) | 
| 977 |  |  |  |  |  |  | } | 
| 978 |  |  |  |  |  |  |  | 
| 979 | 4 |  |  |  |  | 33 | return EXIT_OK; | 
| 980 |  |  |  |  |  |  | } #Main() | 
| 981 |  |  |  |  |  |  |  | 
| 982 |  |  |  |  |  |  | # }}}1 | 
| 983 |  |  |  |  |  |  |  | 
| 984 |  |  |  |  |  |  | # no import() --- call Main() directly with its fully-qualified name | 
| 985 |  |  |  |  |  |  |  | 
| 986 |  |  |  |  |  |  | 1; | 
| 987 |  |  |  |  |  |  | __END__ | 
| 988 |  |  |  |  |  |  | # === Rest of the documentation ========================================= {{{1 | 
| 989 |  |  |  |  |  |  |  | 
| 990 |  |  |  |  |  |  | =head1 AUTHOR | 
| 991 |  |  |  |  |  |  |  | 
| 992 |  |  |  |  |  |  | Christopher White, C<cxwembedded at gmail.com> | 
| 993 |  |  |  |  |  |  |  | 
| 994 |  |  |  |  |  |  | =head1 SUPPORT | 
| 995 |  |  |  |  |  |  |  | 
| 996 |  |  |  |  |  |  | You can find documentation for this module with the perldoc command. | 
| 997 |  |  |  |  |  |  |  | 
| 998 |  |  |  |  |  |  | perldoc App::hopen                      For command-line options | 
| 999 |  |  |  |  |  |  | perldoc App::hopen::Conventions         For terminology and workflow | 
| 1000 |  |  |  |  |  |  | perldoc Data::Hopen                     For internals | 
| 1001 |  |  |  |  |  |  |  | 
| 1002 |  |  |  |  |  |  | You can also look for information at: | 
| 1003 |  |  |  |  |  |  |  | 
| 1004 |  |  |  |  |  |  | =over | 
| 1005 |  |  |  |  |  |  |  | 
| 1006 |  |  |  |  |  |  | =item * GitHub: The project's main repository and issue tracker | 
| 1007 |  |  |  |  |  |  |  | 
| 1008 |  |  |  |  |  |  | L<https://github.com/hopenbuild/App-hopen> | 
| 1009 |  |  |  |  |  |  |  | 
| 1010 |  |  |  |  |  |  | =item * MetaCPAN | 
| 1011 |  |  |  |  |  |  |  | 
| 1012 |  |  |  |  |  |  | L<https://metacpan.org/pod/App::hopen> | 
| 1013 |  |  |  |  |  |  |  | 
| 1014 |  |  |  |  |  |  | =item * This distribution | 
| 1015 |  |  |  |  |  |  |  | 
| 1016 |  |  |  |  |  |  | See the C<eg/> directory distributed with this software for examples. | 
| 1017 |  |  |  |  |  |  |  | 
| 1018 |  |  |  |  |  |  | =back | 
| 1019 |  |  |  |  |  |  |  | 
| 1020 |  |  |  |  |  |  | =head1 LICENSE AND COPYRIGHT | 
| 1021 |  |  |  |  |  |  |  | 
| 1022 |  |  |  |  |  |  | Copyright (c) 2018--2019 Christopher White.  All rights reserved. | 
| 1023 |  |  |  |  |  |  |  | 
| 1024 |  |  |  |  |  |  | This program is free software; you can redistribute it and/or | 
| 1025 |  |  |  |  |  |  | modify it under the terms of the GNU Lesser General Public | 
| 1026 |  |  |  |  |  |  | License as published by the Free Software Foundation; either | 
| 1027 |  |  |  |  |  |  | version 2.1 of the License, or (at your option) any later version. | 
| 1028 |  |  |  |  |  |  |  | 
| 1029 |  |  |  |  |  |  | This program is distributed in the hope that it will be useful, | 
| 1030 |  |  |  |  |  |  | but WITHOUT ANY WARRANTY; without even the implied warranty of | 
| 1031 |  |  |  |  |  |  | MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the GNU | 
| 1032 |  |  |  |  |  |  | Lesser General Public License for more details. | 
| 1033 |  |  |  |  |  |  |  | 
| 1034 |  |  |  |  |  |  | You should have received a copy of the GNU Lesser General Public | 
| 1035 |  |  |  |  |  |  | License along with this program; if not, write to the Free | 
| 1036 |  |  |  |  |  |  | Software Foundation, Inc., | 
| 1037 |  |  |  |  |  |  | 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301  USA | 
| 1038 |  |  |  |  |  |  |  | 
| 1039 |  |  |  |  |  |  | =cut | 
| 1040 |  |  |  |  |  |  |  | 
| 1041 |  |  |  |  |  |  | # }}}1 | 
| 1042 |  |  |  |  |  |  | # vi: set ts=4 sts=4 sw=4 et ai foldmethod=marker: # |