2020-02-13 21:43:24 +02:00
|
|
|
[Inbuild::] Inbuild Control.
|
2020-02-10 12:27:24 +02:00
|
|
|
|
2020-02-13 21:43:24 +02:00
|
|
|
The top-level controller through which client tools use this module.
|
2020-02-10 12:27:24 +02:00
|
|
|
|
2020-02-13 21:43:24 +02:00
|
|
|
@h Phases.
|
|
|
|
Although nothing at all clever happens in this section, it requires careful
|
|
|
|
sequencing to avoid invisible errors coming in because function X assumes
|
|
|
|
that function Y has already been called, or perhaos that it never will
|
|
|
|
be again. The client tool ("the tool") has to work in the following way
|
|
|
|
to avoid those problems.
|
|
|
|
|
|
|
|
Firstly, the inbuild module runs through the following phases in sequence:
|
2020-02-13 01:48:37 +02:00
|
|
|
|
|
|
|
@e CONFIGURATION_INBUILD_PHASE from 1
|
2020-02-13 21:43:24 +02:00
|
|
|
@e PRETINKERING_INBUILD_PHASE
|
2020-02-13 01:48:37 +02:00
|
|
|
@e TINKERING_INBUILD_PHASE
|
2020-02-13 21:43:24 +02:00
|
|
|
@e NESTED_INBUILD_PHASE
|
|
|
|
@e PROJECTED_INBUILD_PHASE
|
|
|
|
@e GOING_OPERATIONAL_INBUILD_PHASE
|
|
|
|
@e OPERATIONAL_INBUILD_PHASE
|
|
|
|
|
|
|
|
@d RUN_ONLY_IN_PHASE(P)
|
|
|
|
if (inbuild_phase < P) internal_error("too soon");
|
|
|
|
if (inbuild_phase > P) internal_error("too late");
|
|
|
|
@d RUN_ONLY_FROM_PHASE(P)
|
|
|
|
if (inbuild_phase < P) internal_error("too soon");
|
|
|
|
@d RUN_ONLY_BEFORE_PHASE(P)
|
|
|
|
if (inbuild_phase >= P) internal_error("too late");
|
2020-02-13 01:48:37 +02:00
|
|
|
|
|
|
|
=
|
|
|
|
int inbuild_phase = CONFIGURATION_INBUILD_PHASE;
|
|
|
|
|
2020-02-13 21:43:24 +02:00
|
|
|
@ Initially, then, we are in the configuration phase. This is when command
|
|
|
|
line processing should be done, and the tool should use the following routines
|
|
|
|
to add and process command line switches handled by inbuild:
|
|
|
|
|
2020-02-27 02:35:17 +02:00
|
|
|
@e INBUILD_INFORM_CLSG
|
|
|
|
@e INBUILD_RESOURCES_CLSG
|
|
|
|
@e INBUILD_INTER_CLSG
|
2020-02-13 21:43:24 +02:00
|
|
|
@e INBUILD_CLSG
|
|
|
|
|
|
|
|
@e NEST_CLSW
|
|
|
|
@e INTERNAL_CLSW
|
|
|
|
@e EXTERNAL_CLSW
|
|
|
|
@e TRANSIENT_CLSW
|
|
|
|
@e KIT_CLSW
|
|
|
|
@e PROJECT_CLSW
|
|
|
|
@e SOURCE_CLSW
|
2020-02-21 00:20:04 +02:00
|
|
|
@e DEBUG_CLSW
|
|
|
|
@e FORMAT_CLSW
|
|
|
|
@e RELEASE_CLSW
|
2020-02-23 14:36:39 +02:00
|
|
|
@e CENSUS_CLSW
|
2020-02-25 01:16:59 +02:00
|
|
|
@e PIPELINE_CLSW
|
|
|
|
@e PIPELINE_FILE_CLSW
|
|
|
|
@e PIPELINE_VARIABLE_CLSW
|
2020-02-26 21:58:32 +02:00
|
|
|
@e RNG_CLSW
|
|
|
|
@e CASE_CLSW
|
2020-02-13 21:43:24 +02:00
|
|
|
|
|
|
|
=
|
|
|
|
void Inbuild::declare_options(void) {
|
|
|
|
RUN_ONLY_IN_PHASE(CONFIGURATION_INBUILD_PHASE)
|
2020-02-27 02:35:17 +02:00
|
|
|
CommandLine::begin_group(INBUILD_INFORM_CLSG, I"for translating Inform source text to Inter");
|
2020-02-13 21:43:24 +02:00
|
|
|
CommandLine::declare_switch(PROJECT_CLSW, L"project", 2,
|
|
|
|
L"work within the Inform project X");
|
2020-02-21 00:20:04 +02:00
|
|
|
CommandLine::declare_boolean_switch(DEBUG_CLSW, L"debug", 1,
|
2020-02-27 02:35:17 +02:00
|
|
|
L"compile with debugging features even on a Release", FALSE);
|
2020-02-21 00:20:04 +02:00
|
|
|
CommandLine::declare_boolean_switch(RELEASE_CLSW, L"release", 1,
|
2020-02-27 02:35:17 +02:00
|
|
|
L"compile a version suitable for a Release build", FALSE);
|
2020-02-21 00:20:04 +02:00
|
|
|
CommandLine::declare_textual_switch(FORMAT_CLSW, L"format", 1,
|
|
|
|
L"compile I6 code suitable for the virtual machine X");
|
2020-02-13 21:43:24 +02:00
|
|
|
CommandLine::declare_switch(SOURCE_CLSW, L"source", 2,
|
|
|
|
L"use file X as the Inform source text");
|
2020-02-23 14:36:39 +02:00
|
|
|
CommandLine::declare_boolean_switch(CENSUS_CLSW, L"census", 1,
|
2020-02-27 02:35:17 +02:00
|
|
|
L"perform an extensions census", FALSE);
|
|
|
|
CommandLine::declare_boolean_switch(RNG_CLSW, L"rng", 1,
|
|
|
|
L"fix the random number generator of the story file (for testing)", FALSE);
|
|
|
|
CommandLine::declare_switch(CASE_CLSW, L"case", 2,
|
|
|
|
L"make any source links refer to the source in extension example X");
|
|
|
|
CommandLine::end_group();
|
|
|
|
|
|
|
|
CommandLine::begin_group(INBUILD_RESOURCES_CLSG, I"for locating resources in the file system");
|
|
|
|
CommandLine::declare_switch(NEST_CLSW, L"nest", 2,
|
|
|
|
L"add the nest at pathname X to the search list");
|
|
|
|
CommandLine::declare_switch(INTERNAL_CLSW, L"internal", 2,
|
|
|
|
L"use X as the location of built-in material such as the Standard Rules");
|
|
|
|
CommandLine::declare_switch(EXTERNAL_CLSW, L"external", 2,
|
|
|
|
L"use X as the user's home for installed material such as extensions");
|
|
|
|
CommandLine::declare_switch(TRANSIENT_CLSW, L"transient", 2,
|
|
|
|
L"use X for transient data such as the extensions census");
|
|
|
|
CommandLine::end_group();
|
|
|
|
|
|
|
|
CommandLine::begin_group(INBUILD_INTER_CLSG, I"for tweaking code generation from Inter");
|
|
|
|
CommandLine::declare_switch(KIT_CLSW, L"kit", 2,
|
|
|
|
L"include Inter code from the kit called X");
|
2020-02-25 01:16:59 +02:00
|
|
|
CommandLine::declare_switch(PIPELINE_CLSW, L"pipeline", 2,
|
|
|
|
L"specify code-generation pipeline");
|
|
|
|
CommandLine::declare_switch(PIPELINE_FILE_CLSW, L"pipeline-file", 2,
|
|
|
|
L"specify code-generation pipeline from file X");
|
|
|
|
CommandLine::declare_switch(PIPELINE_VARIABLE_CLSW, L"variable", 2,
|
|
|
|
L"set pipeline variable X (in form name=value)");
|
2020-02-13 21:43:24 +02:00
|
|
|
CommandLine::end_group();
|
2020-02-25 01:16:59 +02:00
|
|
|
|
|
|
|
Inbuild::set_defaults();
|
2020-02-13 21:43:24 +02:00
|
|
|
}
|
|
|
|
|
2020-02-25 01:16:59 +02:00
|
|
|
text_stream *inter_processing_file = NULL;
|
|
|
|
text_stream *inter_processing_pipeline = NULL;
|
|
|
|
dictionary *pipeline_vars = NULL;
|
2020-02-13 21:43:24 +02:00
|
|
|
pathname *shared_transient_resources = NULL;
|
2020-02-21 00:20:04 +02:00
|
|
|
int this_is_a_debug_compile = FALSE; /* Destined to be compiled with debug features */
|
|
|
|
int this_is_a_release_compile = FALSE; /* Omit sections of source text marked not for release */
|
|
|
|
text_stream *story_filename_extension = NULL; /* What story file we will eventually have */
|
2020-02-23 14:36:39 +02:00
|
|
|
int census_mode = FALSE; /* Running only to update extension documentation */
|
2020-02-26 21:58:32 +02:00
|
|
|
int rng_seed_at_start_of_play = 0; /* The seed value, or 0 if not seeded */
|
2020-02-13 21:43:24 +02:00
|
|
|
|
2020-02-25 01:16:59 +02:00
|
|
|
void Inbuild::set_defaults(void) {
|
|
|
|
inter_processing_pipeline = Str::new();
|
|
|
|
inter_processing_file = I"compile";
|
|
|
|
}
|
|
|
|
|
2020-02-27 02:35:17 +02:00
|
|
|
void Inbuild::set_inter_pipeline(wording W) {
|
|
|
|
inter_processing_pipeline = Str::new();
|
|
|
|
WRITE_TO(inter_processing_pipeline, "%W", W);
|
|
|
|
Str::delete_first_character(inter_processing_pipeline);
|
|
|
|
Str::delete_last_character(inter_processing_pipeline);
|
|
|
|
LOG("Setting pipeline %S\n", inter_processing_pipeline);
|
|
|
|
}
|
|
|
|
|
2020-02-13 21:43:24 +02:00
|
|
|
void Inbuild::option(int id, int val, text_stream *arg, void *state) {
|
|
|
|
RUN_ONLY_IN_PHASE(CONFIGURATION_INBUILD_PHASE)
|
|
|
|
switch (id) {
|
2020-02-21 00:20:04 +02:00
|
|
|
case DEBUG_CLSW: this_is_a_debug_compile = val; break;
|
|
|
|
case FORMAT_CLSW: story_filename_extension = Str::duplicate(arg); break;
|
|
|
|
case RELEASE_CLSW: this_is_a_release_compile = val; break;
|
2020-02-13 21:43:24 +02:00
|
|
|
case NEST_CLSW: Inbuild::add_nest(Pathnames::from_text(arg), GENERIC_NEST_TAG); break;
|
|
|
|
case INTERNAL_CLSW: Inbuild::add_nest(Pathnames::from_text(arg), INTERNAL_NEST_TAG); break;
|
|
|
|
case EXTERNAL_CLSW: Inbuild::add_nest(Pathnames::from_text(arg), EXTERNAL_NEST_TAG); break;
|
|
|
|
case TRANSIENT_CLSW: shared_transient_resources = Pathnames::from_text(arg); break;
|
|
|
|
case KIT_CLSW: Inbuild::request_kit(arg); break;
|
|
|
|
case PROJECT_CLSW:
|
|
|
|
if (Inbuild::set_I7_bundle(arg) == FALSE)
|
|
|
|
Errors::fatal_with_text("can't specify the project twice: '%S'", arg);
|
|
|
|
break;
|
|
|
|
case SOURCE_CLSW:
|
|
|
|
if (Inbuild::set_I7_source(arg) == FALSE)
|
|
|
|
Errors::fatal_with_text("can't specify the source file twice: '%S'", arg);
|
|
|
|
break;
|
2020-02-23 14:36:39 +02:00
|
|
|
case CENSUS_CLSW: census_mode = val; break;
|
2020-02-25 01:16:59 +02:00
|
|
|
case PIPELINE_CLSW: inter_processing_pipeline = Str::duplicate(arg); break;
|
|
|
|
case PIPELINE_FILE_CLSW: inter_processing_file = Str::duplicate(arg); break;
|
|
|
|
case PIPELINE_VARIABLE_CLSW: {
|
|
|
|
match_results mr = Regexp::create_mr();
|
|
|
|
if (Regexp::match(&mr, arg, L"(%c+)=(%c+)")) {
|
|
|
|
if (Str::get_first_char(arg) != '*') {
|
|
|
|
Errors::fatal("-variable names must begin with '*'");
|
|
|
|
} else {
|
|
|
|
#ifdef CODEGEN_MODULE
|
|
|
|
if (pipeline_vars == NULL)
|
|
|
|
pipeline_vars = CodeGen::Pipeline::basic_dictionary(I"output.z8");
|
|
|
|
Str::copy(Dictionaries::create_text(pipeline_vars, mr.exp[0]), mr.exp[1]);
|
|
|
|
#endif
|
|
|
|
}
|
|
|
|
} else {
|
|
|
|
Errors::fatal("-variable should take the form 'name=value'");
|
|
|
|
}
|
|
|
|
Regexp::dispose_of(&mr);
|
|
|
|
break;
|
|
|
|
}
|
2020-02-26 21:58:32 +02:00
|
|
|
case RNG_CLSW:
|
|
|
|
if (val) rng_seed_at_start_of_play = -16339;
|
|
|
|
else rng_seed_at_start_of_play = 0;
|
|
|
|
break;
|
|
|
|
case CASE_CLSW: HTMLFiles::set_source_link_case(arg); break;
|
2020-02-13 21:43:24 +02:00
|
|
|
}
|
|
|
|
}
|
|
|
|
|
|
|
|
@ Once the tool has finished with the command line, it should call this
|
2020-02-21 00:20:04 +02:00
|
|
|
function. Inbuild rapidly runs through the next few phases as it does so.
|
2020-02-13 21:43:24 +02:00
|
|
|
From the "nested" phase, the final list of nests in the search path for
|
|
|
|
finding kits, extensions and so on exists; from the "projected" phase,
|
|
|
|
the main Inform project exists. As we shall see, Inbuild deals with only
|
|
|
|
one Inform project at a time, though it may be handling many kits and
|
|
|
|
extensions, and so on, which are needed by that project.
|
|
|
|
|
|
|
|
=
|
2020-02-21 00:20:04 +02:00
|
|
|
target_vm *current_target_VM = NULL;
|
2020-02-24 01:49:56 +02:00
|
|
|
inbuild_copy *Inbuild::optioneering_complete(inbuild_copy *C, int compile_only) {
|
2020-02-13 21:43:24 +02:00
|
|
|
RUN_ONLY_IN_PHASE(CONFIGURATION_INBUILD_PHASE)
|
2020-02-22 01:16:23 +02:00
|
|
|
|
2020-02-25 01:16:59 +02:00
|
|
|
#ifdef CODEGEN_MODULE
|
|
|
|
if (pipeline_vars == NULL)
|
|
|
|
pipeline_vars = CodeGen::Pipeline::basic_dictionary(I"output.z8");
|
|
|
|
#endif
|
|
|
|
|
2020-02-22 01:16:23 +02:00
|
|
|
target_vm *VM = NULL;
|
|
|
|
text_stream *ext = story_filename_extension;
|
|
|
|
if (Str::len(ext) == 0) ext = I"ulx";
|
|
|
|
int with_debugging = FALSE;
|
|
|
|
if ((this_is_a_release_compile == FALSE) || (this_is_a_debug_compile))
|
|
|
|
with_debugging = TRUE;
|
|
|
|
VM = TargetVMs::find(ext, with_debugging);
|
|
|
|
|
2020-02-13 21:43:24 +02:00
|
|
|
inbuild_phase = PRETINKERING_INBUILD_PHASE;
|
2020-02-22 01:16:23 +02:00
|
|
|
inform_project *project = Inbuild::create_shared_project(C);
|
|
|
|
|
2020-02-13 21:43:24 +02:00
|
|
|
inbuild_phase = TINKERING_INBUILD_PHASE;
|
|
|
|
Inbuild::sort_nest_list();
|
|
|
|
inbuild_phase = NESTED_INBUILD_PHASE;
|
2020-03-05 14:42:33 +02:00
|
|
|
if (project) Projects::set_to_English(project);
|
2020-02-13 21:43:24 +02:00
|
|
|
Inbuild::pass_kit_requests();
|
2020-03-05 14:42:33 +02:00
|
|
|
#ifndef CORE_MODULE
|
|
|
|
if (project) Copies::read_source_text_for(project->as_copy);
|
|
|
|
#endif
|
2020-02-13 21:43:24 +02:00
|
|
|
inbuild_phase = PROJECTED_INBUILD_PHASE;
|
2020-02-22 01:16:23 +02:00
|
|
|
|
|
|
|
if (project) {
|
2020-02-24 01:49:56 +02:00
|
|
|
Projects::construct_build_target(project, VM, this_is_a_release_compile, compile_only);
|
2020-02-22 01:16:23 +02:00
|
|
|
current_target_VM = VM;
|
|
|
|
return project->as_copy;
|
|
|
|
} else {
|
|
|
|
return NULL;
|
|
|
|
}
|
2020-02-13 21:43:24 +02:00
|
|
|
}
|
|
|
|
|
2020-02-21 00:20:04 +02:00
|
|
|
target_vm *Inbuild::current_vm(void) {
|
|
|
|
return current_target_VM;
|
|
|
|
}
|
|
|
|
int Inbuild::currently_releasing(void) {
|
|
|
|
return this_is_a_release_compile;
|
|
|
|
}
|
|
|
|
|
2020-02-13 21:43:24 +02:00
|
|
|
@ Inbuild is now in the "projected" phase, then. The idea is that this
|
|
|
|
is a short interval during which the tool can if it wishes add further
|
|
|
|
kit dependencies to the main project. Once that sort of thing is done,
|
|
|
|
the tool should call the following, which puts Inbuild into its
|
|
|
|
final "operational" phase -- at this point Inbuild is fully configured
|
|
|
|
and ready for use.
|
|
|
|
|
|
|
|
The brief "going operational" phase is used, for example, to build out
|
|
|
|
dependency graphs.
|
|
|
|
|
|
|
|
=
|
2020-02-25 01:16:59 +02:00
|
|
|
inform_project *Inbuild::go_operational(void) {
|
2020-02-13 21:43:24 +02:00
|
|
|
RUN_ONLY_IN_PHASE(PROJECTED_INBUILD_PHASE)
|
|
|
|
inbuild_phase = GOING_OPERATIONAL_INBUILD_PHASE;
|
|
|
|
inbuild_copy *C;
|
|
|
|
LOOP_OVER(C, inbuild_copy)
|
2020-02-17 11:43:20 +02:00
|
|
|
Copies::go_operational(C);
|
2020-02-13 21:43:24 +02:00
|
|
|
inbuild_phase = OPERATIONAL_INBUILD_PHASE;
|
2020-02-23 14:36:39 +02:00
|
|
|
if (census_mode) Extensions::Census::handle_census_mode();
|
2020-02-25 01:16:59 +02:00
|
|
|
return Inbuild::project();
|
2020-02-13 21:43:24 +02:00
|
|
|
}
|
|
|
|
|
2020-02-11 02:15:49 +02:00
|
|
|
@h The nest list.
|
2020-02-26 21:58:32 +02:00
|
|
|
Nests are directories which hold resources to be used by the Intools, and
|
|
|
|
one of Inbuild's main roles is to search and manage nests. All nests can
|
|
|
|
hold extensions, kits, language definitions, and so on.
|
|
|
|
|
|
|
|
But among nests three are special, and can hold other things as well.
|
|
|
|
|
|
|
|
(a) The "internal" nest is part of the installation of Inform as software.
|
|
|
|
It contains, for example, the build-in extensions. But it also contains
|
|
|
|
miscellaneous other files needed by Infomr (see below).
|
|
|
|
|
|
|
|
(b) The "external" nest is the one to which the user installs her own
|
|
|
|
selection of extensions, and so on. On most platforms, the external nest
|
|
|
|
is also the default home of "transient" storage, for more ephemeral content,
|
|
|
|
such as the mechanically generated extension documentation. Some mobile
|
|
|
|
operating systems are aggressive about wanting to delete ephemeral files
|
|
|
|
used by applications, so |-transient| can be used to divert these.
|
|
|
|
|
|
|
|
(c) Every project has its own private nest, in the form of its associated
|
|
|
|
Materials folder. For example, in |Jane Eyre.inform| is a project, then
|
|
|
|
alongside it is |Jane Eyre.materials| and this is a nest.
|
|
|
|
|
2020-02-11 02:15:49 +02:00
|
|
|
Nests used by the Inform and Inbuild tools are tagged with the following
|
|
|
|
comstamts, except that no nest is ever tagged |NOT_A_NEST_TAG|.
|
|
|
|
(There used to be quite a good joke here, but refactoring of the
|
|
|
|
code removed its premiss. Literate programming is like that sometimes.)
|
2020-02-10 12:27:24 +02:00
|
|
|
|
2020-02-11 02:15:49 +02:00
|
|
|
@e NOT_A_NEST_TAG from 0
|
|
|
|
@e MATERIALS_NEST_TAG
|
|
|
|
@e EXTERNAL_NEST_TAG
|
|
|
|
@e GENERIC_NEST_TAG
|
|
|
|
@e INTERNAL_NEST_TAG
|
2020-02-10 12:27:24 +02:00
|
|
|
|
2020-02-11 02:15:49 +02:00
|
|
|
@ Inform customarily has exactly one |-internal| and one |-external| nest,
|
|
|
|
but in fact any number of each are allowed, including none. However, the
|
|
|
|
first to be declared are used by the compiler as "the" internal and external
|
|
|
|
nests, respectively.
|
2020-02-10 12:27:24 +02:00
|
|
|
|
2020-02-11 02:15:49 +02:00
|
|
|
The following hold the nests in declaration order.
|
2020-02-10 12:27:24 +02:00
|
|
|
|
2020-02-11 02:15:49 +02:00
|
|
|
=
|
2020-02-10 12:27:24 +02:00
|
|
|
linked_list *unsorted_nest_list = NULL;
|
|
|
|
inbuild_nest *shared_internal_nest = NULL;
|
|
|
|
inbuild_nest *shared_external_nest = NULL;
|
2020-02-11 02:15:49 +02:00
|
|
|
inbuild_nest *shared_materials_nest = NULL;
|
2020-02-10 12:27:24 +02:00
|
|
|
|
2020-02-13 21:43:24 +02:00
|
|
|
inbuild_nest *Inbuild::add_nest(pathname *P, int tag) {
|
|
|
|
RUN_ONLY_BEFORE_PHASE(TINKERING_INBUILD_PHASE)
|
2020-02-10 12:27:24 +02:00
|
|
|
if (unsorted_nest_list == NULL)
|
|
|
|
unsorted_nest_list = NEW_LINKED_LIST(inbuild_nest);
|
|
|
|
inbuild_nest *N = Nests::new(P);
|
|
|
|
Nests::set_tag(N, tag);
|
|
|
|
ADD_TO_LINKED_LIST(N, inbuild_nest, unsorted_nest_list);
|
|
|
|
if ((tag == EXTERNAL_NEST_TAG) && (shared_external_nest == NULL))
|
|
|
|
shared_external_nest = N;
|
|
|
|
if ((tag == INTERNAL_NEST_TAG) && (shared_internal_nest == NULL))
|
|
|
|
shared_internal_nest = N;
|
|
|
|
if (tag == INTERNAL_NEST_TAG) Nests::protect(N);
|
|
|
|
return N;
|
|
|
|
}
|
|
|
|
|
2020-02-11 02:15:49 +02:00
|
|
|
@ It is then sorted in tag order. This is so that if we look for, say, an
|
|
|
|
extension with a given name, then results in a project's materials folder
|
|
|
|
are given precedence over those in the external folder, and so on.
|
|
|
|
|
|
|
|
=
|
|
|
|
linked_list *shared_nest_list = NULL;
|
2020-02-13 21:43:24 +02:00
|
|
|
void Inbuild::sort_nest_list(void) {
|
|
|
|
RUN_ONLY_IN_PHASE(TINKERING_INBUILD_PHASE)
|
2020-02-11 02:15:49 +02:00
|
|
|
shared_nest_list = NEW_LINKED_LIST(inbuild_nest);
|
|
|
|
inbuild_nest *N;
|
|
|
|
LOOP_OVER_LINKED_LIST(N, inbuild_nest, unsorted_nest_list)
|
|
|
|
if (Nests::get_tag(N) == MATERIALS_NEST_TAG)
|
|
|
|
ADD_TO_LINKED_LIST(N, inbuild_nest, shared_nest_list);
|
|
|
|
LOOP_OVER_LINKED_LIST(N, inbuild_nest, unsorted_nest_list)
|
|
|
|
if (Nests::get_tag(N) == EXTERNAL_NEST_TAG)
|
|
|
|
ADD_TO_LINKED_LIST(N, inbuild_nest, shared_nest_list);
|
|
|
|
LOOP_OVER_LINKED_LIST(N, inbuild_nest, unsorted_nest_list)
|
|
|
|
if (Nests::get_tag(N) == GENERIC_NEST_TAG)
|
|
|
|
ADD_TO_LINKED_LIST(N, inbuild_nest, shared_nest_list);
|
|
|
|
LOOP_OVER_LINKED_LIST(N, inbuild_nest, unsorted_nest_list)
|
|
|
|
if (Nests::get_tag(N) == INTERNAL_NEST_TAG)
|
|
|
|
ADD_TO_LINKED_LIST(N, inbuild_nest, shared_nest_list);
|
|
|
|
}
|
|
|
|
|
|
|
|
@ And the rest of Inform or Inbuild can now use:
|
|
|
|
|
|
|
|
=
|
2020-02-13 21:43:24 +02:00
|
|
|
linked_list *Inbuild::nest_list(void) {
|
|
|
|
RUN_ONLY_FROM_PHASE(NESTED_INBUILD_PHASE)
|
2020-02-11 02:15:49 +02:00
|
|
|
if (shared_nest_list == NULL) internal_error("nest list never sorted");
|
|
|
|
return shared_nest_list;
|
|
|
|
}
|
|
|
|
|
2020-02-13 21:43:24 +02:00
|
|
|
inbuild_nest *Inbuild::internal(void) {
|
|
|
|
RUN_ONLY_FROM_PHASE(NESTED_INBUILD_PHASE)
|
2020-02-10 12:27:24 +02:00
|
|
|
return shared_internal_nest;
|
|
|
|
}
|
|
|
|
|
2020-02-13 21:43:24 +02:00
|
|
|
inbuild_nest *Inbuild::external(void) {
|
|
|
|
RUN_ONLY_FROM_PHASE(NESTED_INBUILD_PHASE)
|
2020-02-10 12:27:24 +02:00
|
|
|
return shared_external_nest;
|
|
|
|
}
|
|
|
|
|
2020-02-13 21:43:24 +02:00
|
|
|
pathname *Inbuild::materials(void) {
|
|
|
|
RUN_ONLY_FROM_PHASE(NESTED_INBUILD_PHASE)
|
2020-02-11 02:15:49 +02:00
|
|
|
if (shared_materials_nest == NULL) return NULL;
|
|
|
|
return shared_materials_nest->location;
|
|
|
|
}
|
|
|
|
|
2020-02-26 21:58:32 +02:00
|
|
|
@ As noted above, the transient area is used for ephemera such as dynamically
|
|
|
|
written documentation and telemetry files. |-transient| sets it, but otherwise
|
2020-02-11 02:15:49 +02:00
|
|
|
the external nest is used.
|
|
|
|
|
|
|
|
=
|
2020-02-13 21:43:24 +02:00
|
|
|
pathname *Inbuild::transient(void) {
|
|
|
|
RUN_ONLY_FROM_PHASE(PROJECTED_INBUILD_PHASE)
|
2020-02-10 12:27:24 +02:00
|
|
|
if (shared_transient_resources == NULL)
|
|
|
|
if (shared_external_nest)
|
|
|
|
return shared_external_nest->location;
|
|
|
|
return shared_transient_resources;
|
|
|
|
}
|
|
|
|
|
2020-02-11 02:15:49 +02:00
|
|
|
@h The shared project.
|
|
|
|
In any single run, each of the Inform tools concerns itself with a single
|
|
|
|
Inform 7 program. This can be presented to it either in a project bundle
|
|
|
|
(a directory which contains source, settings, space for an index and for
|
|
|
|
temporary build files), or as a single file (just a text file containing
|
|
|
|
source text).
|
2020-02-10 12:27:24 +02:00
|
|
|
|
2020-02-11 02:15:49 +02:00
|
|
|
It is also possible o set a folder to be the project bundle, and nevertheless
|
|
|
|
specify a file somewhere else to be the source text. What you can't do is
|
|
|
|
specify the bundle twice, or specify the file twice.
|
2020-02-10 12:27:24 +02:00
|
|
|
|
|
|
|
=
|
2020-02-11 02:15:49 +02:00
|
|
|
text_stream *project_bundle_request = NULL;
|
|
|
|
text_stream *project_file_request = NULL;
|
|
|
|
|
2020-02-13 21:43:24 +02:00
|
|
|
int Inbuild::set_I7_source(text_stream *loc) {
|
|
|
|
RUN_ONLY_FROM_PHASE(CONFIGURATION_INBUILD_PHASE)
|
2020-02-11 02:15:49 +02:00
|
|
|
if (Str::len(project_file_request) > 0) return FALSE;
|
|
|
|
project_file_request = Str::duplicate(loc);
|
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
2020-02-13 21:43:24 +02:00
|
|
|
int Inbuild::set_I7_bundle(text_stream *loc) {
|
|
|
|
RUN_ONLY_FROM_PHASE(CONFIGURATION_INBUILD_PHASE)
|
2020-02-11 02:15:49 +02:00
|
|
|
if (Str::len(project_bundle_request) > 0) return FALSE;
|
|
|
|
project_bundle_request = Str::duplicate(loc);
|
2020-02-11 12:29:10 +02:00
|
|
|
pathname *pathname_of_bundle = Pathnames::from_text(project_bundle_request);
|
2020-02-13 21:43:24 +02:00
|
|
|
pathname *materials = Inbuild::pathname_of_materials(pathname_of_bundle);
|
2020-02-11 12:29:10 +02:00
|
|
|
TEMPORARY_TEXT(leaf);
|
|
|
|
WRITE_TO(leaf, "%s-settings.txt", INTOOL_NAME);
|
|
|
|
filename *expert_settings = Filenames::in_folder(materials, leaf);
|
|
|
|
LOG("Speculatively %f\n", expert_settings);
|
|
|
|
if (TextFiles::exists(expert_settings))
|
|
|
|
CommandLine::also_read_file(expert_settings);
|
|
|
|
DISCARD_TEXT(leaf);
|
2020-02-11 02:15:49 +02:00
|
|
|
return TRUE;
|
|
|
|
}
|
|
|
|
|
|
|
|
@ If a bundle is found, then by default the source text within it is called
|
|
|
|
|story.ni|. The |.ni| is an anachronism now, but at one time stood for
|
|
|
|
"natural Inform", the working title for Inform 7 in the early 2000s.
|
|
|
|
|
|
|
|
=
|
|
|
|
inform_project *shared_project = NULL;
|
|
|
|
|
2020-02-22 01:16:23 +02:00
|
|
|
inform_project *Inbuild::create_shared_project(inbuild_copy *C) {
|
2020-02-13 21:43:24 +02:00
|
|
|
RUN_ONLY_IN_PHASE(PRETINKERING_INBUILD_PHASE)
|
2020-02-11 02:15:49 +02:00
|
|
|
filename *filename_of_i7_source = NULL;
|
|
|
|
pathname *pathname_of_bundle = NULL;
|
|
|
|
if (Str::len(project_bundle_request) > 0) {
|
|
|
|
pathname_of_bundle = Pathnames::from_text(project_bundle_request);
|
2020-02-10 12:27:24 +02:00
|
|
|
}
|
2020-02-11 02:15:49 +02:00
|
|
|
if (Str::len(project_file_request) > 0) {
|
|
|
|
filename_of_i7_source = Filenames::from_text(project_file_request);
|
|
|
|
}
|
2020-02-13 01:48:37 +02:00
|
|
|
if (C) {
|
|
|
|
pathname_of_bundle = C->location_if_path;
|
|
|
|
filename_of_i7_source = C->location_if_file;
|
|
|
|
}
|
|
|
|
if ((pathname_of_bundle) && (filename_of_i7_source == NULL))
|
|
|
|
filename_of_i7_source =
|
|
|
|
Filenames::in_folder(
|
|
|
|
Pathnames::subfolder(pathname_of_bundle, I"Source"),
|
|
|
|
I"story.ni");
|
2020-02-11 02:15:49 +02:00
|
|
|
if (pathname_of_bundle) {
|
2020-02-13 01:48:37 +02:00
|
|
|
if (C == NULL) C = ProjectBundleManager::claim_folder_as_copy(pathname_of_bundle);
|
2020-02-11 02:15:49 +02:00
|
|
|
shared_project = ProjectBundleManager::from_copy(C);
|
|
|
|
} else if (filename_of_i7_source) {
|
2020-02-13 01:48:37 +02:00
|
|
|
if (C == NULL) C = ProjectFileManager::claim_file_as_copy(filename_of_i7_source);
|
2020-02-11 02:15:49 +02:00
|
|
|
shared_project = ProjectFileManager::from_copy(C);
|
|
|
|
}
|
2020-02-22 16:09:13 +02:00
|
|
|
@<Create the default externals nest@>;
|
2020-02-11 02:15:49 +02:00
|
|
|
@<Create the materials nest@>;
|
2020-02-12 01:20:14 +02:00
|
|
|
if (shared_project) {
|
|
|
|
pathname *P = (shared_materials_nest)?(shared_materials_nest->location):NULL;
|
|
|
|
if (P) P = Pathnames::subfolder(P, I"Source");
|
|
|
|
if (Str::len(project_file_request) > 0) P = NULL;
|
|
|
|
Projects::set_source_filename(shared_project, P, filename_of_i7_source);
|
2020-02-26 21:58:32 +02:00
|
|
|
if (rng_seed_at_start_of_play != 0)
|
|
|
|
Projects::fix_rng(shared_project, rng_seed_at_start_of_play);
|
2020-02-12 01:20:14 +02:00
|
|
|
}
|
2020-02-22 01:16:23 +02:00
|
|
|
return shared_project;
|
2020-02-11 02:15:49 +02:00
|
|
|
}
|
|
|
|
|
2020-02-22 16:09:13 +02:00
|
|
|
@<Create the default externals nest@> =
|
|
|
|
inbuild_nest *E = shared_external_nest;
|
|
|
|
if (E == NULL) {
|
|
|
|
pathname *P = home_path;
|
|
|
|
char *subfolder_within = INFORM_FOLDER_RELATIVE_TO_HOME;
|
|
|
|
if (subfolder_within[0]) {
|
|
|
|
TEMPORARY_TEXT(SF);
|
|
|
|
WRITE_TO(SF, "%s", subfolder_within);
|
|
|
|
P = Pathnames::subfolder(home_path, SF);
|
|
|
|
DISCARD_TEXT(SF);
|
|
|
|
}
|
|
|
|
P = Pathnames::subfolder(P, I"Inform");
|
|
|
|
E = Inbuild::add_nest(P, EXTERNAL_NEST_TAG);
|
|
|
|
}
|
|
|
|
|
2020-02-11 02:15:49 +02:00
|
|
|
@<Create the materials nest@> =
|
|
|
|
pathname *materials = NULL;
|
|
|
|
if (pathname_of_bundle) {
|
2020-02-13 21:43:24 +02:00
|
|
|
materials = Inbuild::pathname_of_materials(pathname_of_bundle);
|
2020-02-11 02:15:49 +02:00
|
|
|
Pathnames::create_in_file_system(materials);
|
|
|
|
} else if (filename_of_i7_source) {
|
|
|
|
materials = Pathnames::from_text(I"inform.materials");
|
|
|
|
}
|
|
|
|
if (materials) {
|
2020-02-13 21:43:24 +02:00
|
|
|
shared_materials_nest = Inbuild::add_nest(materials, MATERIALS_NEST_TAG);
|
2020-02-11 02:15:49 +02:00
|
|
|
}
|
|
|
|
|
|
|
|
@ And the rest of Inform or Inbuild can now use:
|
|
|
|
|
|
|
|
=
|
2020-02-13 21:43:24 +02:00
|
|
|
inform_project *Inbuild::project(void) {
|
2020-03-04 21:34:23 +02:00
|
|
|
RUN_ONLY_FROM_PHASE(TINKERING_INBUILD_PHASE)
|
2020-02-11 02:15:49 +02:00
|
|
|
return shared_project;
|
|
|
|
}
|
|
|
|
|
2020-02-11 12:29:10 +02:00
|
|
|
@ The materials folder sits alongside the project folder and has the same name,
|
|
|
|
but ending |.materials| instead of |.inform|.
|
|
|
|
|
|
|
|
=
|
2020-02-13 21:43:24 +02:00
|
|
|
pathname *Inbuild::pathname_of_materials(pathname *pathname_of_bundle) {
|
2020-02-11 12:29:10 +02:00
|
|
|
TEMPORARY_TEXT(mf);
|
|
|
|
WRITE_TO(mf, "%S", Pathnames::directory_name(pathname_of_bundle));
|
|
|
|
int i = Str::len(mf)-1;
|
|
|
|
while ((i>0) && (Str::get_at(mf, i) != '.')) i--;
|
|
|
|
if (i>0) {
|
|
|
|
Str::truncate(mf, i);
|
|
|
|
WRITE_TO(mf, ".materials");
|
|
|
|
}
|
|
|
|
pathname *materials = Pathnames::subfolder(Pathnames::up(pathname_of_bundle), mf);
|
|
|
|
DISCARD_TEXT(mf);
|
|
|
|
return materials;
|
|
|
|
}
|
|
|
|
|
2020-02-12 01:20:14 +02:00
|
|
|
@h Kit requests.
|
2020-02-13 21:43:24 +02:00
|
|
|
These are triggered by, for example, |-kit MyFancyKit| at the command line.
|
|
|
|
For timing reasons, we store those up in the configuration phase and then
|
|
|
|
add them as dependencies only when a project exists.
|
2020-02-12 01:20:14 +02:00
|
|
|
|
|
|
|
=
|
|
|
|
linked_list *kits_requested_at_command_line = NULL;
|
2020-02-13 21:43:24 +02:00
|
|
|
void Inbuild::request_kit(text_stream *name) {
|
|
|
|
RUN_ONLY_IN_PHASE(CONFIGURATION_INBUILD_PHASE)
|
2020-02-12 01:20:14 +02:00
|
|
|
if (kits_requested_at_command_line == NULL)
|
|
|
|
kits_requested_at_command_line = NEW_LINKED_LIST(text_stream);
|
|
|
|
text_stream *kit_name;
|
|
|
|
LOOP_OVER_LINKED_LIST(kit_name, text_stream, kits_requested_at_command_line)
|
|
|
|
if (Str::eq(kit_name, name))
|
|
|
|
return;
|
|
|
|
ADD_TO_LINKED_LIST(Str::duplicate(name), text_stream, kits_requested_at_command_line);
|
|
|
|
}
|
|
|
|
|
2020-02-13 21:43:24 +02:00
|
|
|
void Inbuild::pass_kit_requests(void) {
|
|
|
|
RUN_ONLY_IN_PHASE(NESTED_INBUILD_PHASE)
|
2020-02-12 01:20:14 +02:00
|
|
|
if ((shared_project) && (kits_requested_at_command_line)) {
|
|
|
|
text_stream *kit_name;
|
|
|
|
LOOP_OVER_LINKED_LIST(kit_name, text_stream, kits_requested_at_command_line) {
|
|
|
|
Projects::add_kit_dependency(shared_project, kit_name);
|
|
|
|
Projects::not_necessarily_parser_IF(shared_project);
|
|
|
|
}
|
|
|
|
}
|
|
|
|
}
|
|
|
|
|
2020-02-26 12:02:06 +02:00
|
|
|
@h Installation.
|
2020-02-26 21:58:32 +02:00
|
|
|
Inform needs a whole pile of files to have been installed on the host computer
|
|
|
|
before it can run: everything from the Standard Rules to a PDF file explaining
|
|
|
|
what interactive fiction is. They're never written to, only read. They are
|
|
|
|
referred to as "internal" or "built-in", and they occupy a folder called the
|
|
|
|
"internal resources" folder.
|
|
|
|
|
|
|
|
Unfortunately we don't know where it is. Typically this compiler will be an
|
|
|
|
executable sitting somewhere inside a user interface application, and the
|
|
|
|
internal resources folder will be somewhere else inside it. But we don't
|
|
|
|
know how to find that folder, and we don't want to make any assumptions.
|
|
|
|
This is the purpose of the internal nest, and this is why inform7 can only
|
|
|
|
be run if an |-internal| switch has been used specifying where it is.
|
|
|
|
|
|
|
|
The internal nest has two additional subfolders (additional in that they
|
|
|
|
don't hold copies in the Inbuild sense, just a bunch of loose odds and ends):
|
|
|
|
|Miscellany| and |HTML|. Many of these files are to help Inblorb to perform
|
|
|
|
a release.
|
|
|
|
|
|
|
|
The documentation snippets file is generated by |indoc| and contains
|
|
|
|
brief specifications of phrases, extracted from the manual "Writing with
|
|
|
|
Inform". This is used to generate the Phrasebook index.
|
|
|
|
|
|
|
|
Anyway, Inform and its associated tools can then access these files using the
|
|
|
|
following routine:
|
2020-02-26 12:02:06 +02:00
|
|
|
|
|
|
|
@e CBLORB_REPORT_MODEL_IRES from 1
|
|
|
|
@e DOCUMENTATION_SNIPPETS_IRES
|
|
|
|
@e INTRO_BOOKLET_IRES
|
|
|
|
@e INTRO_POSTCARD_IRES
|
|
|
|
@e LARGE_DEFAULT_COVER_ART_IRES
|
|
|
|
@e SMALL_DEFAULT_COVER_ART_IRES
|
|
|
|
@e DOCUMENTATION_XREFS_IRES
|
|
|
|
@e JAVASCRIPT_FOR_STANDARD_PAGES_IRES
|
|
|
|
@e JAVASCRIPT_FOR_EXTENSIONS_IRES
|
|
|
|
@e JAVASCRIPT_FOR_ONE_EXTENSION_IRES
|
|
|
|
@e CSS_FOR_STANDARD_PAGES_IRES
|
|
|
|
@e EXTENSION_DOCUMENTATION_MODEL_IRES
|
2020-02-11 12:29:10 +02:00
|
|
|
|
2020-02-26 12:02:06 +02:00
|
|
|
=
|
|
|
|
filename *Inbuild::file_from_installation(int ires) {
|
|
|
|
inbuild_nest *I = Inbuild::internal();
|
|
|
|
if (I == NULL) Errors::fatal("Did not set -internal when calling");
|
|
|
|
pathname *misc = Pathnames::subfolder(I->location, I"Miscellany");
|
|
|
|
pathname *models = Pathnames::subfolder(I->location, I"HTML");
|
|
|
|
switch (ires) {
|
|
|
|
case DOCUMENTATION_SNIPPETS_IRES: return Filenames::in_folder(misc, I"definitions.html");
|
|
|
|
case INTRO_BOOKLET_IRES: return Filenames::in_folder(misc, I"IntroductionToIF.pdf");
|
|
|
|
case INTRO_POSTCARD_IRES: return Filenames::in_folder(misc, I"Postcard.pdf");
|
|
|
|
case LARGE_DEFAULT_COVER_ART_IRES: return Filenames::in_folder(misc, I"Cover.jpg");
|
|
|
|
case SMALL_DEFAULT_COVER_ART_IRES: return Filenames::in_folder(misc, I"Small Cover.jpg");
|
2020-02-26 21:58:32 +02:00
|
|
|
|
|
|
|
case CBLORB_REPORT_MODEL_IRES: return Filenames::in_folder(models, I"CblorbModel.html");
|
2020-02-26 12:02:06 +02:00
|
|
|
case DOCUMENTATION_XREFS_IRES: return Filenames::in_folder(models, I"xrefs.txt");
|
|
|
|
case JAVASCRIPT_FOR_STANDARD_PAGES_IRES: return Filenames::in_folder(models, I"main.js");
|
|
|
|
case JAVASCRIPT_FOR_EXTENSIONS_IRES: return Filenames::in_folder(models, I"extensions.js");
|
|
|
|
case JAVASCRIPT_FOR_ONE_EXTENSION_IRES: return Filenames::in_folder(models, I"extensionfile.js");
|
|
|
|
case CSS_FOR_STANDARD_PAGES_IRES: return Filenames::in_folder(models, I"main.css");
|
|
|
|
case EXTENSION_DOCUMENTATION_MODEL_IRES: return Filenames::in_folder(models, I"extensionfile.html");
|
2020-02-26 21:58:32 +02:00
|
|
|
}
|
2020-02-26 12:02:06 +02:00
|
|
|
internal_error("unknown installation resource file");
|
|
|
|
return NULL;
|
|
|
|
}
|