Products and Modules
Smallworld Magik-Sources and Resources are organised within modules, defined by a module.def file in their root directory, which defines amongst other things the name and version of the modules and the names (and versions) of other modules, which are required to be loaded before the code of the defined module. The source code files for the module are defined by load_list.txt files in the module's root directory and recursively in child directories, as long as they are listed in their parent directory's load_list.txt file.
Several Modules may be organised within layered products, defined by a product.def file in their root directory. Layered product may also be located below another layered product. The call smallworld_product.add_product(<path-to-a-product's-root-directory>) makes the product and all its modules (including the layered products in subdirectories and its modules) known to the current session.
Then a module is known to the session, its code can be loaded by sw_module_manager.load_module(<name-of-the-module>). If some of the required modules are not yet loaded, their code is loaded before.
There are some techniques how code loading is accelerated:
up to SW 4.3
When loading a module the code is loaded from compiled magik files (*.magikc) if they are available and newer than the corresponding *.magik file. If not, the code is loaded from a *magik file, if available, and by default a *magikc file is generated for accelerating the next load of the module.
SW 5.0
Compiled magik files are no longer supported. Instead the compiled code of a module may be saved to single *jar file in a libs directory of the top most layered product, in which the module resides. The jar file's name includes its layered product's and module's name separated by a dot.
Furthermore the initialising of a product may be accelerated by genearting a *ser file of the product's structure. If such a file is present, the smallworld_product.add_product() command reads all informations on modules etc. from this file without parsing the directory tree.
Dependencies on the loading order of modules
In an ideal world all modules depend only on their required modules and besides that they are completely independent. If each module defines only methods on classes defined within that module, and each class is defined within one modules only, we should be quite sure to have such an ideal constellation. In fact there are many possibilities to produce depencencies which cause the result of loading some modules depends on the order in which these modules are loaded. And probably for each possibility you'll find at least one example in a module GE or a partner sells to customers.
Unfold the following points to see details:
- Description: Two modules define the same method in a different way (on a class, defined by a third module, which is required by both modules).
- Impact: In this case, obviously the definition of the module loaded at last wins.
- Workaround: If cannot change that, you may define your own module, requiring both modules and define that method in the way you need it.
- Avoid: There is no established general mechanism for hooking within magik. So there is only a general advice: Mimimise changes to existing code of other modules.
- Description: Part of the code of a module is loaded only, if another module is already loaded. Instead of testing sw_module_manager.module(<another-module>).loaded? this may also be done by testing something like !current_package![:<a-special-class_name>] _isnt _unset or similar. The conditional loading may be done by a load_file, a load_file_list, or even a sw_module_manager.load_module(<a-module-to-be-loaded>) command within the _if control structure.
- Impact:
- real module dependencies are not displayed correctly (e.g. the output of sw_module_manager.print_prerequisite_information()
- if module is loaded too early in the build process, it may happen that necessary code is not loaded, as the de facto prerequisites are loaded later on
- Workaround: If such modules are used, be sure to load them at last
- Avoid: As such a technique often is used to control the loaded glue code: it would be clearer to implement glue module for each possible required module. The product's documentation should declare, which one(s) has/have to be loaded.
- Description:
- Module a defines a class class_a inheriting from mixin_a, and the methods mixin_a.a_method() and class_a.a_method(). The method class_a.a_method() includes a call _super.a_method(). Furtheron class_a also inherits from mixin_b
- Module b defines a method mixin_b.a_method().
- Impact:
- The method mixin_b.a_method() will never be called when calling class_a.a_method(). This is due to the fact, that when compiling mixin_a.a_method() the method mixin_b.a_method() is not yet known to the system. (But if you try to recompile class_a.a_method(), you'll get a compiler error Too many super implementations of a_method() )
- When does this happen? Imagine module a belongs to a product of GE or a partner, and module b is part of your customisation. When implementing your customisation, you should quickly detect, that your method will not be called. In fact I've seen this situation not on the first implementation, but after module a having changed the class hierarchy and the customisation hasn't been adapted the right way. Afterwards the no-longer-called method mixin_b.a_method() stayed within the customisation code for years
- Workaround: depends on situation
- Avoid: Code Review
- Description: Module a uses a global, which is defined in module b, but the module.def file of module a doesn't require module b to be loaded before.
- Impact:
- if the global is used during compile time, it has the value _unset, if module b isnt loaded before (e.g. think of object.define_shared_constant(:my_shared_const,<the_global_from_module_b>,:public: in this case object.my_shared_const yields _unset, even if module b is loaded afterwards)
- (see example code below) if different packages are used, the auto declared global within module a hides the global of module b, if the following conditions are fulfilled:
- module a is loaded before module b
- when loading the code of module a the !current_package! is a package inheriting from the package, in which the global is defined within module b
- the global is used without qualifiing the package explicitly (e.g. as g and not sw:g)
- if you are using *magikc-files (until SW 4.3) or a *jar-file (since SW 5.0) of module a, the package of an auto declared global is stored within these files. So if the auto declared global resides in the wrong package: For repairing that defect, you have to delete these files before loading the modules in the correct order (b before a)
- Workaround: Include module b in the requires list of the module.def file of module a
- Avoid:
- It's a good practice to load code with the global !global_auto_declare?! set to _false. Than you will detect all such situations with missing requires entries. Unfortunately some of the code delivered by GE and its partners isn't loadable with that setting. In that case you may design your build process in a way, that the !global_auto_declare? global is set to true at least when loading your own code.
- Sometimes it happens, that in some methods you are using a class, which isn't defined yet (which is effectively an implicit forward declaration). It is often an option to use !current_package![:<my-class-name>] instead, or include a real forward declaration line: _global <class_name> << !current_package![:<my-class-name>], which works also, if loaded after having defined the class.
- Magik-on-Java stuff (SW 5.0):
- if a GE or third party product is delivered with *jar files, the it's the vendorer's responsibility to generate these files in the right order. When you load these *jar files the result should be independent of the setting of the global !global_auto_declare?!
- in some cases the compiler automatically creates globals. E.g. if you use a _leave statement within a loop, the compiler auto generates a global __leave_catcher__, if not existing yet. So if you want to load your code with !global_auto_declare?! set to _false, you have to think of loading a few lines like _for i _over range(0,0) _loop _leave _endloop before switching off the auto declaration of globals.
- if you create jar files for a product without having set the global !global_auto_declare?! set to _false, and you are not sure having all requires set correctly, you might think about what you chose as first argument of <a-product>.compile_all_modules(<initial modules>).
- this load order dependency is logged as defect CBG00159122
Here example code for the case with an auto declared global hiding another from another package:
# code within module a
_package user
object.define_shared_constant(:my_val,g,:public)
# code within module b
_package sw
def_slotted_exemplar(:g,{},{})
# if module b is loaded after module a
Magik> object.my_val
_unset
Magik> @g
global_variable(:user:g)
Magik> g
_unset
Magik> sw:g
a sw:g
A remark on *jar file generating with SW 5.0
When using the <a-product>.compile_all_modules() in most of the a.m. examples the result doesn't depend at all on the question, whether the *jar file(s) are already generated. An important exception is the auto declaration of globals in different packages as described above.
SW_USER_CUSTOM
The sw_user_custom directory typically located under the user's home directory can be created and have the same directory layout as any other customization product. The issue that isn't well defined in the documentation is the how the user's home directory is defined. The documentation mentions "HOME" environment. This is not accurate. the home directory that is used is from system.user Which isn't HOME, it is the combination of HOMEDRIVE and HOMEPATH environments.