1
0
Fork 0
mirror of https://github.com/ganelson/inform.git synced 2024-06-29 05:24:57 +03:00
inform7/inbuild/Chapter 1/Main.w
2022-03-31 12:32:39 +01:00

389 lines
15 KiB
OpenEdge ABL

[Main::] Main.
A command-line interface for Inbuild functions which are not part of the
normal operation of the Inform compiler.
@h Settings variables.
The following will be set at the command line.
=
pathname *path_to_inbuild = NULL;
int inbuild_task = INSPECT_TTASK;
pathname *path_to_tools = NULL;
int dry_run_mode = FALSE, build_trace_mode = FALSE;
inbuild_nest *destination_nest = NULL;
text_stream *filter_text = NULL;
@h Main routine.
When Inbuild is called at the command line, it begins at |main|, like all C
programs.
Inbuild manages "copies", which are instances of programs or resources found
somewhere in the file system. The copies which it acts on in a given run are
called "targets". The task of |main| is to read the command-line arguments,
set the following variables as needed, and produce a list of targets to work
on; then to carry out that work, and then shut down again.
=
int main(int argc, char **argv) {
@<Start up the modules@>;
@<Read the command line@>;
CommandLine::play_back_log();
@<Complete the list of targets@>;
@<Act on the targets@>;
@<Shut down the modules@>;
if (Errors::have_occurred()) return 1;
return 0;
}
@<Start up the modules@> =
Foundation::start(argc, argv); /* must be started first */
WordsModule::start();
SyntaxModule::start();
HTMLModule::start();
ArchModule::start();
SupervisorModule::start();
@ Targets can arise in three ways:
(1) They can be specified at the command line, either as bare names of files
or paths, or with |-contents-of D| for a directory |D|. By the time the code
in this paragraph runs, those targets are already in the list.
(2) They can be specified by a search request |-matching R| where |R| is a
list of requirements to match. We now add anything found that way. (We didn't
do so when reading the command line because at that time the search path for
nests did not yet exist: it is created when |Supervisor::optioneering_complete|
is called.)
(3) One copy is always special to Inbuild: the "project", usually an Inform
project bundle with a pathname like |Counterfeit Monkey.inform|. We go
through a little dance with |Supervisor::optioneering_complete| to ensure that
if a project is already in the target list, Inbuild will use that; and if not,
but the user has specified a project to Inbuild already with |-project| (a
command-line option recognised by |inform7| but not by us), then we add that
to the target list. Tne net result is that however the user indicates interest
in an Inform project bundle, it becomes both the Inbuild current project, and
also a member of our target list. It follows that we cannot have two project
bundles in the target list, because they cannot both be the current project;
and to avoid the user being confused when only one is acted on, we throw an
error in this case.
@<Complete the list of targets@> =
linked_list *L = Main::list_of_targets();
inbuild_copy *D = NULL, *C;
LOOP_OVER_LINKED_LIST(C, inbuild_copy, L)
if ((C->edition->work->genre == project_bundle_genre) ||
(C->edition->work->genre == project_file_genre))
D = C;
Supervisor::optioneering_complete(D, FALSE, &Main::load_preform);
inform_project *proj;
LOOP_OVER(proj, inform_project)
Main::add_target(proj->as_copy);
int count = 0;
LOOP_OVER_LINKED_LIST(C, inbuild_copy, L)
if ((C->edition->work->genre == project_bundle_genre) ||
(C->edition->work->genre == project_file_genre))
count++;
if (count > 1)
Errors::with_text("can only work on one project bundle at a time", NULL);
if (Str::len(filter_text) > 0) Main::add_search_results_as_targets(filter_text);
@ We make the function call |Supervisor::go_operational| to signal to |inbuild|
that we want to start work now.
@<Act on the targets@> =
Supervisor::go_operational();
int use = SHELL_METHODOLOGY;
if (dry_run_mode) use = DRY_RUN_METHODOLOGY;
build_methodology *BM;
if (path_to_tools) BM = BuildMethodology::new(path_to_tools, FALSE, use);
else BM = BuildMethodology::new(Pathnames::up(path_to_inbuild), TRUE, use);
if (build_trace_mode) IncrementalBuild::enable_trace();
linked_list *L = Main::list_of_targets();
inbuild_copy *C;
LOOP_OVER_LINKED_LIST(C, inbuild_copy, L)
@<Carry out the required task on the copy C@>;
@ The list of possible tasks is as follows; they basically all correspond to
utility functions in the //supervisor// module, which we call.
@e INSPECT_TTASK from 1
@e GRAPH_TTASK
@e USE_NEEDS_TTASK
@e BUILD_NEEDS_TTASK
@e ARCHIVE_TTASK
@e ARCHIVE_TO_TTASK
@e USE_MISSING_TTASK
@e BUILD_MISSING_TTASK
@e BUILD_TTASK
@e REBUILD_TTASK
@e COPY_TO_TTASK
@e SYNC_TO_TTASK
@<Carry out the required task on the copy C@> =
switch (inbuild_task) {
case INSPECT_TTASK: Copies::inspect(STDOUT, C); break;
case GRAPH_TTASK: Copies::show_graph(STDOUT, C); break;
case USE_NEEDS_TTASK: Copies::show_needs(STDOUT, C, TRUE); break;
case BUILD_NEEDS_TTASK: Copies::show_needs(STDOUT, C, FALSE); break;
case ARCHIVE_TTASK: {
inform_project *proj;
int c = 0;
LOOP_OVER(proj, inform_project) {
c++;
destination_nest = Projects::materials_nest(proj);
}
if (c == 0)
Errors::with_text("no -project in use, so ignoring -archive", NULL);
else if (c > 1)
Errors::with_text("multiple projects in use, so ignoring -archive", NULL);
else
Copies::archive(STDOUT, C, destination_nest, BM);
break;
}
case ARCHIVE_TO_TTASK: Copies::archive(STDOUT, C, destination_nest, BM); break;
case USE_MISSING_TTASK: Copies::show_missing(STDOUT, C, TRUE); break;
case BUILD_MISSING_TTASK: Copies::show_missing(STDOUT, C, FALSE); break;
case BUILD_TTASK: Copies::build(STDOUT, C, BM); break;
case REBUILD_TTASK: Copies::rebuild(STDOUT, C, BM); break;
case COPY_TO_TTASK: Copies::copy_to(C, destination_nest, FALSE, BM); break;
case SYNC_TO_TTASK: Copies::copy_to(C, destination_nest, TRUE, BM); break;
}
@<Shut down the modules@> =
ArchModule::end();
SupervisorModule::end();
HTMLModule::end();
SyntaxModule::end();
WordsModule::end();
Foundation::end(); /* must be ended last */
@ Preform is the crowning jewel of the |words| module, and parses excerpts of
natural-language text against a "grammar". The |inform7| executable makes very
heavy-duty use of Preform, but we use a much coarser grammar, which simply
breaks down source text into sentences, headings and so on. That grammar is
stored in a file called |Syntax.preform| inside the installation of Inbuild,
which is why we need to have worked out |path_to_inbuild| (the pathname at
which we are installed) already. Once the following is run, Preform is ready
for use.
=
void Main::load_preform(inform_language *L) {
pathname *P = Pathnames::down(path_to_inbuild, I"Tangled");
filename *S = Filenames::in(P, I"Syntax.preform");
LoadPreform::load(S, NULL);
}
@h Target list.
This where we keep the list of targets, in which no copy occurs more than
once. The following code runs quadratically in the number of targets, but for
Inbuild this number is never likely to be more than about 100 at a time.
=
linked_list *targets = NULL; /* of |inbuild_copy| */
void Main::add_target(inbuild_copy *to_add) {
if (targets == NULL) targets = NEW_LINKED_LIST(inbuild_copy);
int found = FALSE;
inbuild_copy *C;
LOOP_OVER_LINKED_LIST(C, inbuild_copy, targets)
if (C == to_add)
found = TRUE;
if (found == FALSE) ADD_TO_LINKED_LIST(to_add, inbuild_copy, targets);
}
@ The following sorts the list of targets before returning it. This is partly
to improve the quality of the output of |-inspect|, but also to make the
behaviour of //inbuild// more predictable across platforms -- the raw target
list tends to be in order of discovery of the copies, which in turn depends on
the order in which filenames are read from a directory listing.
=
linked_list *Main::list_of_targets(void) {
if (targets == NULL) targets = NEW_LINKED_LIST(inbuild_copy);
int no_entries = LinkedLists::len(targets);
if (no_entries == 0) return targets;
inbuild_copy **sorted_targets =
Memory::calloc(no_entries, sizeof(inbuild_copy *), EXTENSION_DICTIONARY_MREASON);
int i=0;
inbuild_copy *C;
LOOP_OVER_LINKED_LIST(C, inbuild_copy, targets) sorted_targets[i++] = C;
qsort(sorted_targets, (size_t) no_entries, sizeof(inbuild_copy *), Copies::cmp);
linked_list *result = NEW_LINKED_LIST(inbuild_copy);
for (int i=0; i<no_entries; i++)
ADD_TO_LINKED_LIST(sorted_targets[i], inbuild_copy, result);
Memory::I7_array_free(sorted_targets, EXTENSION_DICTIONARY_MREASON,
no_entries, sizeof(inbuild_copy *));
return result;
}
void Main::add_search_results_as_targets(text_stream *req_text) {
TEMPORARY_TEXT(errors)
inbuild_requirement *req = Requirements::from_text(req_text, errors);
if (Str::len(errors) > 0) {
Errors::with_text("requirement malformed: %S", errors);
} else {
linked_list *L = NEW_LINKED_LIST(inbuild_search_result);
Nests::search_for(req, Supervisor::shared_nest_list(), L);
inbuild_search_result *R;
LOOP_OVER_LINKED_LIST(R, inbuild_search_result, L)
Main::add_target(R->copy);
}
DISCARD_TEXT(errors)
}
void Main::add_directory_contents_targets(pathname *P) {
scan_directory *D = Directories::open(P);
TEMPORARY_TEXT(LEAFNAME)
while (Directories::next(D, LEAFNAME)) {
TEMPORARY_TEXT(FILENAME)
WRITE_TO(FILENAME, "%p%c%S", P, FOLDER_SEPARATOR, LEAFNAME);
Main::add_file_or_path_as_target(FILENAME, FALSE);
DISCARD_TEXT(FILENAME)
}
DISCARD_TEXT(LEAFNAME)
Directories::close(D);
}
void Main::add_file_or_path_as_target(text_stream *arg, int throwing_error) {
TEMPORARY_TEXT(ext)
int pos = Str::len(arg) - 1, dotpos = -1;
while (pos >= 0) {
wchar_t c = Str::get_at(arg, pos);
if (Platform::is_folder_separator(c)) break;
if (c == '.') dotpos = pos;
pos--;
}
if (dotpos >= 0)
Str::substr(ext, Str::at(arg, dotpos+1), Str::end(arg));
int directory_status = NOT_APPLICABLE;
if (Platform::is_folder_separator(Str::get_last_char(arg))) {
Str::delete_last_character(arg);
directory_status = TRUE;
}
inbuild_copy *C = NULL;
inbuild_genre *G;
LOOP_OVER(G, inbuild_genre)
if (C == NULL)
VOID_METHOD_CALL(G, GENRE_CLAIM_AS_COPY_MTID, &C, arg, ext, directory_status);
DISCARD_TEXT(ext)
if (C == NULL) {
if (throwing_error) Errors::with_text("unable to identify '%S'", arg);
return;
}
Main::add_target(C);
}
@h Command line.
Note the call below to |Supervisor::declare_options|, which adds a whole lot of
other options to the selection defined here.
@d PROGRAM_NAME "inbuild"
@e BUILD_CLSW
@e REBUILD_CLSW
@e GRAPH_CLSW
@e USE_NEEDS_CLSW
@e BUILD_NEEDS_CLSW
@e USE_MISSING_CLSW
@e BUILD_MISSING_CLSW
@e ARCHIVE_CLSW
@e ARCHIVE_TO_CLSW
@e INSPECT_CLSW
@e DRY_CLSW
@e BUILD_TRACE_CLSW
@e TOOLS_CLSW
@e CONTENTS_OF_CLSW
@e MATCHING_CLSW
@e COPY_TO_CLSW
@e SYNC_TO_CLSW
@<Read the command line@> =
CommandLine::declare_heading(
L"[[Purpose]]\n\n"
L"usage: inbuild [-TASK] TARGET1 TARGET2 ...\n");
CommandLine::declare_switch(COPY_TO_CLSW, L"copy-to", 2,
L"copy target(s) to nest X");
CommandLine::declare_switch(SYNC_TO_CLSW, L"sync-to", 2,
L"forcibly copy target(s) to nest X, even if prior version already there");
CommandLine::declare_switch(BUILD_CLSW, L"build", 1,
L"incrementally build target(s)");
CommandLine::declare_switch(REBUILD_CLSW, L"rebuild", 1,
L"completely rebuild target(s)");
CommandLine::declare_switch(INSPECT_CLSW, L"inspect", 1,
L"show target(s) but take no action");
CommandLine::declare_switch(GRAPH_CLSW, L"graph", 1,
L"show dependency graph of target(s) but take no action");
CommandLine::declare_switch(USE_NEEDS_CLSW, L"use-needs", 1,
L"show all the extensions, kits and so on needed to use");
CommandLine::declare_switch(BUILD_NEEDS_CLSW, L"build-needs", 1,
L"show all the extensions, kits and so on needed to build");
CommandLine::declare_switch(USE_MISSING_CLSW, L"use-missing", 1,
L"show the extensions, kits and so on which are needed to use but missing");
CommandLine::declare_switch(BUILD_MISSING_CLSW, L"build-missing", 1,
L"show the extensions, kits and so on which are needed to build but missing");
CommandLine::declare_switch(ARCHIVE_CLSW, L"archive", 1,
L"sync copies of all extensions, kits and so on needed for -project into Materials");
CommandLine::declare_switch(ARCHIVE_TO_CLSW, L"archive-to", 2,
L"sync copies of all extensions, kits and so on needed into nest X");
CommandLine::declare_switch(TOOLS_CLSW, L"tools", 2,
L"make X the directory of intools executables, and exit developer mode");
CommandLine::declare_boolean_switch(DRY_CLSW, L"dry", 1,
L"make this a dry run (print but do not execute shell commands)", FALSE);
CommandLine::declare_boolean_switch(BUILD_TRACE_CLSW, L"build-trace", 1,
L"show verbose reasoning during -build", FALSE);
CommandLine::declare_switch(MATCHING_CLSW, L"matching", 2,
L"apply to all works in nest(s) matching requirement X");
CommandLine::declare_switch(CONTENTS_OF_CLSW, L"contents-of", 2,
L"apply to all targets in the directory X");
Supervisor::declare_options();
CommandLine::read(argc, argv, NULL, &Main::option, &Main::bareword);
if (LinkedLists::len(unsorted_nest_list) == 0)
Supervisor::add_nest(
Pathnames::from_text(I"inform7/Internal"), INTERNAL_NEST_TAG);
path_to_inbuild = Pathnames::installation_path("INBUILD_PATH", I"inbuild");
@ Here we handle those options not handled by the //supervisor// module.
=
void Main::option(int id, int val, text_stream *arg, void *state) {
switch (id) {
case BUILD_CLSW: inbuild_task = BUILD_TTASK; break;
case REBUILD_CLSW: inbuild_task = REBUILD_TTASK; break;
case INSPECT_CLSW: inbuild_task = INSPECT_TTASK; break;
case GRAPH_CLSW: inbuild_task = GRAPH_TTASK; break;
case USE_NEEDS_CLSW: inbuild_task = USE_NEEDS_TTASK; break;
case BUILD_NEEDS_CLSW: inbuild_task = BUILD_NEEDS_TTASK; break;
case ARCHIVE_TO_CLSW:
destination_nest = Nests::new(Pathnames::from_text(arg));
inbuild_task = ARCHIVE_TO_TTASK;
break;
case ARCHIVE_CLSW: inbuild_task = ARCHIVE_TTASK; break;
case USE_MISSING_CLSW: inbuild_task = USE_MISSING_TTASK; break;
case BUILD_MISSING_CLSW: inbuild_task = BUILD_MISSING_TTASK; break;
case TOOLS_CLSW: path_to_tools = Pathnames::from_text(arg); break;
case MATCHING_CLSW: filter_text = Str::duplicate(arg); break;
case CONTENTS_OF_CLSW:
Main::add_directory_contents_targets(Pathnames::from_text(arg)); break;
case DRY_CLSW: dry_run_mode = val; break;
case BUILD_TRACE_CLSW: build_trace_mode = val; break;
case COPY_TO_CLSW: inbuild_task = COPY_TO_TTASK;
destination_nest = Nests::new(Pathnames::from_text(arg));
break;
case SYNC_TO_CLSW: inbuild_task = SYNC_TO_TTASK;
destination_nest = Nests::new(Pathnames::from_text(arg));
break;
}
Supervisor::option(id, val, arg, state);
}
@ This is called for a command-line argument which doesn't appear as
subordinate to any switch; we take it as the name of a copy.
=
void Main::bareword(int id, text_stream *arg, void *state) {
Main::add_file_or_path_as_target(arg, TRUE);
}