note description: "Managing information generated by the consumer." legal: "See notice at end of class." status: "See notice at end of class." date: "$Date$" revision: "$Revision$" class CONF_CONSUMER_MANAGER inherit CACHE_CONSTANTS export {NONE} all end SHARED_CONSUMER_UTILITIES export {NONE} all end EXCEPTIONS export {NONE} all end CONF_FILE_DATE export {NONE} all end CONF_ACCESS SHARED_LOCALE create make feature {NONE} -- Initialization make (a_factory: like factory; a_metadata_cache_path: PATH; an_il_version: like il_version; a_application_target: like application_target; an_added_classes, a_removed_classes, a_modified_classes: SEARCH_TABLE [CONF_CLASS]) -- Create. require a_factory_ok: a_factory /= Void a_metadata_cache_path_ok: a_metadata_cache_path /= Void and then not a_metadata_cache_path.is_empty an_il_version_ok: an_il_version /= Void a_application_target_ok: a_application_target /= Void an_added_classes_ok: an_added_classes /= Void a_removed_classes_ok: a_removed_classes /= Void a_modified_classes_ok: a_modified_classes /= Void do factory := a_factory metadata_cache_path := a_metadata_cache_path il_version := an_il_version application_target := a_application_target added_classes := an_added_classes removed_classes := a_removed_classes modified_classes := a_modified_classes create consume_assembly_observer create linear_assemblies.make (0) ensure factory_set: factory = a_factory metadata_cache_path_set: metadata_cache_path = a_metadata_cache_path il_version_set: an_il_version /= Void implies il_version = an_il_version application_target_set: application_target = a_application_target added_classes_set: added_classes = an_added_classes removed_classes_set: removed_classes = a_removed_classes modified_classes_set: modified_classes = a_modified_classes end feature -- Status is_error: BOOLEAN -- Was there an error? do Result := last_error /= Void end last_error: detachable CONF_ERROR -- Last error. feature -- Access application_target: CONF_TARGET -- Application target of the system. il_version: STRING_32 -- IL version to use. metadata_cache_path: PATH -- Location of the metadata cache. full_cache_path: PATH -- Final location where the metadata realy is stored. do Result := metadata_cache_path.extended (short_cache_name).extended (cache_bit_platform).extended (il_version) end assemblies: detachable STRING_TABLE [CONF_PHYSICAL_ASSEMBLY] -- Assemblies in the system after compilation indexed by their UUID. feature -- Observers consume_assembly_observer: ACTION_SEQUENCE [TUPLE] -- Observer if assemblies are consumed. feature -- Commands build_assemblies (a_new_assemblies: attached like new_assemblies; an_old_assemblies: detachable STRING_TABLE [CONF_PHYSICAL_ASSEMBLY_INTERFACE]) -- Build information about `a_new_assemblies' from the metadata cache and `an_old_assemblies' and store them in `assemblies'. require a_new_assemblies_not_void: a_new_assemblies /= Void local l_retried: BOOLEAN o: like old_assemblies an_assembly: CONF_PHYSICAL_ASSEMBLY ca: CONSUMED_ASSEMBLY l_chained_forwarded_types: ARRAYED_LIST [TUPLE [forwarded_type: CONSUMED_FORWARDED_TYPE; physical_assembly: CONF_PHYSICAL_ASSEMBLY]] l_forwarded_assemblies: STRING_TABLE [CONF_ASSEMBLY] do if not l_retried then -- load information from metadata cache, consume if the metadata cache does not yet exist retrieve_cache if cache_content = Void then consume_all_assemblies (a_new_assemblies) end check cache_retrieved: cache_content /= Void end o := an_old_assemblies if attached o then old_assemblies := o check_old_assemblies_in_cache o.linear_representation.do_all (agent {CONF_PHYSICAL_ASSEMBLY}.reset_assemblies) else create o.make (0) old_assemblies := o end new_assemblies := a_new_assemblies create assemblies.make (o.count) linear_assemblies.wipe_out -- Go through all the assemblies and update the information if necessary. from a_new_assemblies.start until a_new_assemblies.after loop -- build the assembly, -- consume if necessary build_assembly (a_new_assemblies.item_for_iteration) a_new_assemblies.forth end -- build/add dependencies from linear_assemblies.start until linear_assemblies.after loop an_assembly := linear_assemblies.item build_dependencies (an_assembly) -- Then handle Forwarded types if attached an_assembly.consumed_assembly.forwarded_types as l_fwd_types and then not l_fwd_types.is_empty then -- Update information for forwarded types of `an_assembly`. across l_fwd_types as ft loop if attached {CONSUMED_FORWARDED_TYPE} ft as l_fwd_type and then l_fwd_type.assembly = Void and then -- This type was already processed attached an_assembly.dependencies as l_assembly_dependencies and then attached l_assembly_dependencies [l_fwd_type.assembly_id] as l_fwd_assembly then ca := l_fwd_assembly.consumed_assembly l_fwd_type.set_assembly (ca) -- Create and add to the CONF_TARGET, the CONF_ASSEMBLY-s to support forwarded types if l_forwarded_assemblies = Void then create l_forwarded_assemblies.make (1) end register_forwarded_type_assembly (an_assembly, ca, l_forwarded_assemblies) if ca.has_forwarded_type (l_fwd_type) then if l_chained_forwarded_types = Void then create l_chained_forwarded_types.make (1) end l_chained_forwarded_types.force ([l_fwd_type, an_assembly]) end end end end linear_assemblies.forth end if l_chained_forwarded_types /= Void then -- Resolve forwarded types chain. across l_chained_forwarded_types as ft loop if attached ft as l_fwd_type_tuple and then attached l_fwd_type_tuple.forwarded_type as l_fwd_type then an_assembly := l_fwd_type_tuple.physical_assembly ca := l_fwd_type.assembly if ca /= Void then from until not attached ca.forwarded_type_for_dotnet_name (l_fwd_type.dotnet_name) as f_t loop if attached f_t.assembly as f_ca then ca := f_ca l_fwd_type.set_assembly (f_ca) -- Create and add to the CONF_TARGET, the CONF_ASSEMBLY-s to support forwarded types if l_forwarded_assemblies = Void then create l_forwarded_assemblies.make (1) end register_forwarded_type_assembly (an_assembly, ca, l_forwarded_assemblies) else -- At this point, all forwarded types should have an associated CONSUMED_ASSEMBLY object. check has_assembly: False end end end end end end end end if attached internal_consumer.item as e then e.release end ensure assemblies_set: not is_error implies assemblies /= Void rescue check is_error: is_error end if attached exception_manager.last_exception as x and then attached {CONF_EXCEPTION} x.original as lt_ex then l_retried := True retry end end register_forwarded_type_assembly (a_physical_assembly: CONF_PHYSICAL_ASSEMBLY; ca: CONSUMED_ASSEMBLY; a_forwarded_assemblies_cache: STRING_TABLE [CONF_ASSEMBLY]) -- Register assemblies implementing the forwarded types from `ca` in the context of declared assembly `a_physical_assembly`. -- Use `a_forwarded_assemblies_cache` as a cache to avoid having un-needed CONF_ASSEMBLY duplication. require a_physical_assembly /= Void ca /= Void a_forwarded_assemblies_cache /= Void local l_forwarded_assembly: CONF_ASSEMBLY loc, k: READABLE_STRING_GENERAL l_assembly_target: CONF_TARGET do -- Create and add to the CONF_TARGET, the CONF_ASSEMBLY-s to support forwarded types loc := ca.location.name across a_physical_assembly.assemblies as l_assembly loop l_assembly_target := l_assembly.target k := l_assembly_target.name + {STRING_32} "#" + loc l_forwarded_assembly := a_forwarded_assemblies_cache [k] if l_forwarded_assembly = Void then debug ("consumer") print ({STRING_32} "Import assembly related to forwarded-type: " + ca.name + " [target:" + l_assembly_target.name + "]%N") end l_forwarded_assembly := factory.new_assembly (ca.name, loc, l_assembly_target) a_forwarded_assemblies_cache [k] := l_forwarded_assembly l_assembly_target.add_assembly (l_forwarded_assembly) if attached get_physical_assembly (ca) as l_physical_assembly then l_forwarded_assembly.set_physical_assembly (l_physical_assembly) l_physical_assembly.set_is_dependency (False) set_renamed_classes (l_forwarded_assembly) end else check forwarded_assembly_already_added: l_assembly_target.assemblies.has_item (l_forwarded_assembly) end end end end feature {NONE} -- Events on_consume_assemblies -- Assemblies of are consumed. do consume_assembly_observer.call (Void) end feature {NONE} -- Implementation modified_classes: SEARCH_TABLE [CONF_CLASS] -- The list of modified classes. added_classes: SEARCH_TABLE [CONF_CLASS] -- The list of added classes. removed_classes: SEARCH_TABLE [CONF_CLASS] -- The list of removed classes. linear_assemblies: ARRAYED_LIST [CONF_PHYSICAL_ASSEMBLY] -- Linear list of assemblies we have to process. factory: CONF_FACTORY -- Factory to create new configuration nodes. cache_content: detachable CACHE_INFO -- Content of the metadata cache. old_assemblies: detachable STRING_TABLE [CONF_PHYSICAL_ASSEMBLY_INTERFACE] -- Old assemblies from previous compilation. new_assemblies: detachable SEARCH_TABLE [CONF_ASSEMBLY] -- List of assemblies to process. system_runtime_assembly: detachable CONSUMED_ASSEMBLY -- System Runtime Assembly -- Workaround: to replace references to System.Private.CoreLib get_physical_assembly (a_consumed: CONSUMED_ASSEMBLY): CONF_PHYSICAL_ASSEMBLY -- Get the physical assembly for `a_consumed'. require a_consumed_ok: a_consumed /= Void assemblies_attached: attached assemblies local l_guid: READABLE_STRING_32 do check from_precondition: attached assemblies as a then -- see if we already have information about this assembly l_guid := a_consumed.unique_id Result := a.item (l_guid) if Result = Void then -- see if we have information from a previous compilation if attached old_assemblies as o and then attached {CONF_PHYSICAL_ASSEMBLY} o.item (l_guid) as l_as_i then Result := l_as_i else check old_unset: attached old_assemblies as o implies not attached o.item (l_guid) end end if attached Result then if attached old_assemblies as o then o.remove (l_guid) end Result.set_target (application_target) -- has the assembly been modified? if Result.has_date_changed then -- update information Result.set_consumed_assembly (a_consumed) rebuild_classes (Result) Result.set_date end else -- create a new physical assembly Result := factory.new_physical_assembly (a_consumed, full_cache_path, application_target) rebuild_classes (Result) end a.force (Result, l_guid) linear_assemblies.force (Result) end end ensure Result_valid: Result /= Void and then Result.is_valid Result_computed: Result.classes_set Result_date_valid: Result.date > 0 end build_assembly (a_assembly: CONF_ASSEMBLY) -- Build information for `a_assembly'. require a_assembly_ok: a_assembly /= Void and then a_assembly.is_valid assemblies_attached: attached assemblies local l_physical_assembly: CONF_PHYSICAL_ASSEMBLY do if attached consumed_assembly (a_assembly) as l_consumed_assembly then l_physical_assembly := get_physical_assembly (l_consumed_assembly) a_assembly.set_physical_assembly (l_physical_assembly) l_physical_assembly.set_is_dependency (False) else -- In this case, `consumed_assembly' added errors check is_error: is_error end end set_renamed_classes (a_assembly) ensure physical_assembly_set: a_assembly.physical_assembly /= Void classes_set: a_assembly.classes_set end set_renamed_classes (a_assembly: CONF_ASSEMBLY) -- Set classes on `a_assembly' taking them from the physical assembly and applying renamings if necessary. require a_assembly_ok: a_assembly /= Void and then a_assembly.is_valid a_assembly_physical_assembly_set: a_assembly.physical_assembly /= Void local l_new_classes: STRING_TABLE [CONF_CLASS] l_renaming_group: CONF_RENAMING_GROUP l_name: STRING_32 l_dotnet_renaming_groups: STRING_TABLE [CONF_RENAMING_GROUP] do if attached a_assembly.physical_assembly as a and then attached a.classes as l_classes then if not l_classes.is_empty and then attached a_assembly.target.dotnet_renaming as l_dotnet_renaming and then not l_dotnet_renaming.is_empty then -- TODO Check libraries target dotnet_renaming ... l_dotnet_renaming_groups := l_dotnet_renaming end l_renaming_group := a_assembly -- Do we have any renamings? if not l_renaming_group.has_renaming_or_prefix and l_dotnet_renaming_groups = Void then a_assembly.set_classes (l_classes) else create l_new_classes.make (l_classes.count) across l_classes as c loop if attached {CONF_CLASS_ASSEMBLY} c as l_class then create l_name.make_from_string (l_class.name) -- "prefix" is the default "renaming", but if a "renaming" exists -- do not apply the "prefix" if l_renaming_group.has_renaming_or_prefix then l_name := renamed_name (l_class.dotnet_name, l_name, l_renaming_group) end if l_dotnet_renaming_groups /= Void and then not l_dotnet_renaming_groups.is_empty and then attached dotnet_renaming_group_for (l_class.dotnet_name, l_dotnet_renaming_groups) as grp then l_name := renamed_name (l_class.dotnet_name, l_name, grp) end l_new_classes.force (l_class, l_name) debug ("consumer") if not l_name.is_case_insensitive_equal_general (l_class.name) then print ({STRING_32} "RENAME " + l_class.dotnet_name + " {" + l_class.name + "} -> " + l_name + " from [" + l_class.group.name + "]%N") end end else -- In assemblies there are only CONF_CLASS_ASSEMBLY. check has_only_conf_class_assembly: False end end end a_assembly.set_classes (l_new_classes) end else check a_assembly_ok_has_classes: False end end ensure classes_set: a_assembly.classes_set end renamed_name (a_dotnet_name, a_name: STRING_32; a_renaming_grp: CONF_RENAMING_GROUP): STRING_32 -- Renamed name for `a_name` according to the ` a_renaming_grp` group. do if attached a_renaming_grp.renaming as l_renamings and then attached l_renamings.item (a_name) as l_found_item then Result := l_found_item -- FIXME: should has "twin" ? elseif attached a_renaming_grp.name_prefix as l_prefix then Result := l_prefix + a_name else Result := a_name end end dotnet_renaming_group_for (a_dotnet_classname: READABLE_STRING_GENERAL; a_namespace_renaming_groups: STRING_TABLE [CONF_RENAMING_GROUP]): detachable CONF_RENAMING_GROUP -- Dotnet renaming group based on dotnet namespace for dotnet class `a_dotnet_classname`, from `a_namespace_renaming_groups`. local ns: READABLE_STRING_GENERAL i: INTEGER do -- TODO: eventually support wildcard such as "System.*" i := a_dotnet_classname.last_index_of ('.', a_dotnet_classname.count) if i > 0 then ns := a_dotnet_classname.substring (1, i - 1) Result := a_namespace_renaming_groups [ns] end end build_dependencies (an_assembly: CONF_PHYSICAL_ASSEMBLY) -- Build dependencies for `an_assembly'. require an_assembly_ok: an_assembly /= Void assemblies_attached: attached assemblies local l_guid: READABLE_STRING_32 l_reader: EIFFEL_DESERIALIZER do l_guid := an_assembly.guid -- we have to get the dependencies from the reference file l_reader := {EIFFEL_SERIALIZATION}.deserializer_for_version (il_version) l_reader.deserialize (an_assembly.consumed_path.extended (referenced_assemblies_info_file).name, 0) if attached {CONSUMED_ASSEMBLY_MAPPING} l_reader.deserialized_object as l_referenced_assemblies_mapping then system_runtime_assembly := l_referenced_assemblies_mapping.system_runtime_assembly across l_referenced_assemblies_mapping.assemblies as a loop -- if it's not the assembly itself if not a.unique_id.same_string (l_guid) then an_assembly.add_dependency (get_physical_assembly (a), @ a.target_index) end end else check deserialisation_failed: not l_reader.successful end add_error (create {CONF_METADATA_CORRUPT}) end end rebuild_classes (a_assembly: CONF_PHYSICAL_ASSEMBLY) -- (Re)build the list of classes in `a_assembly'. require a_assembly_ok: a_assembly /= Void local l_old_dotnet_classes: detachable STRING_TABLE [CONF_CLASS] l_forwarded_types: detachable ARRAYED_LIST [CONSUMED_FORWARDED_TYPE] l_reader: EIFFEL_DESERIALIZER i, cnt: INTEGER l_name, l_dotnet_name: detachable STRING l_pos: INTEGER l_new_classes, l_new_dotnet_classes: STRING_TABLE [CONF_CLASS] l_class: CONF_CLASS_ASSEMBLY aid: INTEGER fwd_ct: CONSUMED_FORWARDED_TYPE do l_reader := {EIFFEL_SERIALIZATION}.deserializer_for_version (il_version) -- Twin old classes because we will remove reused classes. if attached a_assembly.dotnet_classes as l_assembly_dotnet_classes then l_old_dotnet_classes := l_assembly_dotnet_classes.twin else create l_old_dotnet_classes.make (0) end -- Get classes. l_reader.deserialize (a_assembly.consumed_path.extended (types_info_file).name, 0) if attached {CONSUMED_ASSEMBLY_TYPES} l_reader.deserialized_object as l_types then a_assembly.set_date -- Add classes. create l_new_classes.make (l_old_dotnet_classes.count) create l_new_dotnet_classes.make (l_old_dotnet_classes.count) check l_forwarded_types = Void end from i := l_types.eiffel_names.lower cnt := l_types.eiffel_names.upper check same_dotnet: l_types.dotnet_names.lower = i same_dotnet: l_types.dotnet_names.upper = cnt end until i > cnt loop l_name := l_types.eiffel_names.item (i) l_dotnet_name := l_types.dotnet_names.item (i) l_pos := l_types.positions.item (i) if l_name /= Void and then not l_name.is_empty and then l_dotnet_name /= Void then aid := l_types.assembly_ids.item (i) if aid > 0 then create fwd_ct.make (l_dotnet_name, l_name, aid) if l_forwarded_types = Void then create l_forwarded_types.make (cnt) end l_forwarded_types.force (fwd_ct) else l_name.to_upper if attached {CONF_CLASS_ASSEMBLY} l_old_dotnet_classes.item (l_dotnet_name) as l_dotnet_class then l_class := l_dotnet_class l_old_dotnet_classes.remove (l_dotnet_name) l_class.set_group (a_assembly) l_class.set_type_position (l_pos) if l_class.is_compiled then modified_classes.force (l_class) end else -- Workaround to replace System.Private.* (especially System.Private.CoreLib and System.Private.Uri) by System.Runtime -- WARNING: this workaround is not safe -- Indeed, not every type that is exported in System.Runtime is defined in System.Private.CoreLib or System.Private.Uri. -- And it is not correct to assume that every class defined in System.Private.CoreLib.dll and System.Private.Uri.dll -- will be available from System.Runtime.dll -- -- Options: -- -- 1: Use Reference Assemblies instead of SDK Assemblies to use only Public APIs with nemdc and -- the Eiffel consumer interface. -- 2: Use SDK assemblies, but verify the corresponding assembly for each class. -- it requires to read and inspect different reference assemblies. -- We can build a cache so we can query and get the defined assembly. -- Defined for example at: dotnet\packs\Microsoft.NETCore.App.Ref\6.0.16\ref\net6.0 l_class := factory.new_class_assembly (l_name, l_dotnet_name, a_assembly, l_pos) if attached system_runtime_assembly as l_system_runtime_assembly and then a_assembly.name.as_lower.starts_with_general ("system.private.") then l_class.set_public_group (factory.new_physical_assembly (l_system_runtime_assembly, full_cache_path, application_target)) end added_classes.force (l_class) end l_new_classes.force (l_class, l_name) l_new_dotnet_classes.force (l_class, l_dotnet_name) end end i := i + 1 end -- Classes in l_old_dotnet_classes are not used any more, mark them as removed. across l_old_dotnet_classes as o loop if attached {CONF_CLASS_ASSEMBLY} o as c then c.invalidate if c.is_compiled then removed_classes.force (c) end else check is_conf_class_assembly: False end end end a_assembly.set_classes (l_new_classes) a_assembly.set_dotnet_classes (l_new_dotnet_classes) if l_forwarded_types /= Void then l_forwarded_types.trim end a_assembly.consumed_assembly.set_forwarded_types (l_forwarded_types) else check deserialisation_failed: not l_reader.successful end add_error (create {CONF_METADATA_CORRUPT}) end ensure date_set: a_assembly.date > 0 and then not a_assembly.has_date_changed end feature {CONSUMER_EXPORT} -- Information building rebuild_assembly (a_assembly: CONF_PHYSICAL_ASSEMBLY) -- (Re)build `a_assembly' that was not fully consumed before but is fully consumed now. require a_assembly_ok: a_assembly /= Void do -- this is only called if we fully consume a partially consumed assembly and therefore we always have -- a consumed assembly retrieve_cache if attached consumed_local_assembly (a_assembly.consumed_assembly.location) as l_assembly then a_assembly.set_consumed_assembly (l_assembly) else -- An error is automatically added, so no need for check False end. check is_error: is_error end end rebuild_classes (a_assembly) ensure date_set: a_assembly.date > 0 and then not a_assembly.has_date_changed end feature {NONE} -- retrieving information from cache consumed_assembly (an_assembly: CONF_ASSEMBLY): detachable CONSUMED_ASSEMBLY -- Retrieve (and consume if necessary) consumed information about `an_assembly'. require an_assembly_ok: an_assembly /= Void local l_path: PATH l_file_name: detachable READABLE_STRING_32 do -- was this a non local assembly? if an_assembly.is_non_local_assembly then Result := consumed_gac_assembly (an_assembly) if Result = Void then -- (re)consume the assembly consume_gac_assembly (an_assembly) Result := consumed_gac_assembly (an_assembly) end else -- It is a local assembly l_path := an_assembly.location.evaluated_path Result := consumed_local_assembly (l_path) if Result = Void and then attached new_assemblies as a then -- (re)consume all local assemblies consume_local_assemblies (a) Result := consumed_local_assembly (l_path) end end if Result = Void or else not Result.is_consumed then l_file_name := an_assembly.target.system.file_name if an_assembly.is_non_local_assembly then add_error (create {CONF_ERROR_ASOP}.make (an_assembly.name, l_file_name)) else add_error (create {CONF_ERROR_ASOP}.make (an_assembly.location.original_path, l_file_name)) end end ensure Result_not_void: not is_error implies Result /= Void end consumed_local_assembly (a_location: PATH): detachable CONSUMED_ASSEMBLY -- Retrieve the consumed assembly for a local assembly in `a_location' if we have up to date information in the cache. require cache_content_set: cache_content /= Void a_location_set: a_location /= Void and then not a_location.is_empty local l_formated_location: PATH l_as: CONSUMED_ASSEMBLY do -- find the assembly in the cache if attached cache_content as c then l_formated_location := a_location.canonical_path across c.assemblies as a until attached Result loop l_as := a if l_as.has_same_path (l_formated_location) then Result := l_as end end -- check if the cache information is up to date if Result /= Void and then not is_cache_up_to_date (Result) then Result := Void end end end consumed_gac_assembly (an_assembly: CONF_ASSEMBLY): detachable CONSUMED_ASSEMBLY -- Retrieve the consumed assembly for `an_assembly' that is only specified by gac information if we have up to date information in the cache. require cache_content_set: cache_content /= Void an_assembly_ok: an_assembly /= Void and then an_assembly.is_non_local_assembly local l_as: CONSUMED_ASSEMBLY do if attached an_assembly.assembly_name as l_name and attached an_assembly.assembly_version as l_version and attached an_assembly.assembly_culture as l_culture and attached an_assembly.assembly_public_key_token as l_key then if attached cache_content as c then across c.assemblies as cache_assemblies until attached Result loop l_as := cache_assemblies if l_as.has_same_gac_information (l_name, l_version, l_culture, l_key) then Result := l_as end end -- check if the cache information is up to date if Result /= Void and then not is_cache_up_to_date (Result) then Result := Void end end else check precondition__an_assembly_ok: False end end end feature {NONE} -- Consuming consume_all_assemblies (an_assemblies: SEARCH_TABLE [CONF_ASSEMBLY]) -- Consume all (local and gac) assemblies in `an_assemblies'. require an_assemblies_not_void: an_assemblies /= Void local l_a: CONF_ASSEMBLY l_paths: ARRAYED_LIST [READABLE_STRING_32] l_path: READABLE_STRING_GENERAL l_unique_paths: STRING_TABLE [BOOLEAN] do create l_unique_paths.make_caseless (10) on_consume_assemblies if attached consumer as l_emitter then create l_paths.make (an_assemblies.count) across an_assemblies as l_assembly loop l_a := l_assembly if l_a.is_non_local_assembly then if attached l_a.assembly_name as l_assembly_name and attached l_a.assembly_version as l_assembly_version and attached l_a.assembly_culture as l_assembly_culture and attached l_a.assembly_public_key_token as l_assembly_public_key_token then l_emitter.consume_assembly (l_assembly_name, l_assembly_version, l_assembly_culture, l_assembly_public_key_token, True) if attached l_emitter.last_error_message as m then add_error (create {CONF_METADATA_CONSUMER_FAILURE}.make ({SHARED_LOCALE}.locale.formatted_string ({SHARED_LOCALE}.locale.translation_in_context ({STRING_32} "Error when loading .NET metadata for %"$1%": $2.", "configuration.compiler"), l_assembly_name, m))) end else check is_non_local_assembly: False end end else l_path := l_a.location.evaluated_path.name if not l_unique_paths.has (l_path) then l_unique_paths.force (True, l_path) l_paths.extend (l_path) end end end if not l_paths.is_empty then l_emitter.consume_assembly_from_path (l_paths, True, Void) if attached l_emitter.last_error_message as m then add_error (create {CONF_METADATA_CONSUMER_FAILURE}.make ({SHARED_LOCALE}.locale.formatted_string ({SHARED_LOCALE}.locale.translation_in_context ({STRING_32} "Error when loading .NET assemblies metadata: $1.", "configuration.compiler"), m))) end end end retrieve_cache if cache_content = Void then add_error (create {CONF_METADATA_CORRUPT}) end ensure cache_content_set: cache_content /= Void end consume_local_assemblies (an_assemblies: SEARCH_TABLE [CONF_ASSEMBLY]) -- Consume all local assemblies in `an_assemblies'. require an_assemblies_not_void: an_assemblies /= Void local l_a: CONF_ASSEMBLY l_paths: ARRAYED_LIST [READABLE_STRING_32] l_path: READABLE_STRING_GENERAL l_unique_paths: STRING_TABLE [BOOLEAN] do create l_unique_paths.make_caseless (10) on_consume_assemblies create l_paths.make (an_assemblies.count) across an_assemblies as l_assembly loop l_a := l_assembly if not l_a.is_non_local_assembly then l_path := l_a.location.evaluated_path.name if not l_unique_paths.has (l_path) then l_unique_paths.force (True, l_path) l_paths.extend (l_path) end end end if not l_paths.is_empty then if attached consumer as l_emitter then l_emitter.consume_assembly_from_path (l_paths, True, Void) if attached l_emitter.last_error_message as m then add_error (create {CONF_METADATA_CONSUMER_FAILURE}.make ({SHARED_LOCALE}.locale.formatted_string ({SHARED_LOCALE}.locale.translation_in_context ({STRING_32} "Error when loading .NET assemblies metadata: $1.", "configuration.compiler"), m))) end end end retrieve_cache if cache_content = Void then add_error (create {CONF_METADATA_CORRUPT}) end ensure cache_content_set: cache_content /= Void end consume_gac_assembly (an_assembly: CONF_ASSEMBLY) -- Consume `an_assembly' which was specified without a location. require an_assembly_ok: an_assembly /= Void and then an_assembly.is_non_local_assembly do on_consume_assemblies if attached consumer as l_emitter and then (attached an_assembly.assembly_name as l_assembly_name and then not l_assembly_name.is_empty) and (attached an_assembly.assembly_version as l_assembly_version and then not l_assembly_version.is_empty) and (attached an_assembly.assembly_culture as l_assembly_culture and then not l_assembly_culture.is_empty) and (attached an_assembly.assembly_public_key_token as l_assembly_public_key_token and then not l_assembly_public_key_token.is_empty) then l_emitter.consume_assembly (l_assembly_name, l_assembly_version, l_assembly_culture, l_assembly_public_key_token, True) if attached l_emitter.last_error_message as m then add_error (create {CONF_METADATA_CONSUMER_FAILURE}.make ({SHARED_LOCALE}.locale.formatted_string ({SHARED_LOCALE}.locale.translation_in_context ({STRING_32} "Error when loading metadata for %"$1%": $2.", "configuration.compiler"), l_assembly_name, m))) end else check an_assembly_ok: False end end retrieve_cache if cache_content = Void then add_error (create {CONF_METADATA_CORRUPT}) end ensure cache_content_set: cache_content /= Void end feature {NONE} -- error handling add_error (an_error: CONF_ERROR) -- Add `an_error' and raise an exception. require an_error_not_void: an_error /= Void local l_conf_exception: CONF_EXCEPTION do last_error := an_error create l_conf_exception l_conf_exception.set_description (an_error.text) l_conf_exception.raise end feature {CONSUMER_EXPORT} -- Consumer consumer: detachable CONSUMER -- Instance of a consumer. do Result := internal_consumer.item if Result = Void then Result := {CONSUMER_FACTORY}.consumer (full_cache_path, il_version) if not attached Result then add_error (create {CONF_ERROR_EMITTER_INIT}.make (locale.formatted_string (locale.translation_in_context ({STRING_32} "Cannot obtain a .NET assembly metadata consumer for IL version $1.", "configuration.compiler"), il_version))) elseif not Result.is_available then -- Consumer could not be loaded. add_error (create {CONF_ERROR_EMITTER}) Result.release Result := Void elseif not Result.is_initialized then -- Consumer cannot be initialized (e.g., due to the error in the path). add_error (create {CONF_ERROR_EMITTER_INIT}.make (if attached Result.last_error_message as m then {SHARED_LOCALE}.locale.formatted_string ({SHARED_LOCALE}.locale.translation_in_context ({STRING_32} "Cannot initialize a .NET assembly metadata consumer: $1.", "configuration.compiler"), m) else {SHARED_LOCALE}.locale.translation_in_context ({STRING_32} "Cannot initialize a .NET assembly metadata consumer.", "configuration.compiler") end)) Result.release Result := Void else internal_consumer.put (Result) end end ensure valid_result: Result /= Void implies Result.is_available and then Result.is_initialized end internal_consumer: CELL [detachable CONSUMER] -- Unique instance of IL_EMITTER once create Result.put (Void) ensure result_not_void: Result /= Void end feature {NONE} -- helpers retrieve_cache -- Try to retrieve the information from the metadata cache. local l_reader: EIFFEL_DESERIALIZER l_eac_file: PATH l_file: RAW_FILE do -- Reset cached cache content cache_content := Void l_eac_file := full_cache_path.extended (cache_info_file) create l_file.make_with_path (l_eac_file) if l_file.exists and then l_file.is_readable then l_reader := {EIFFEL_SERIALIZATION}.deserializer_for_version (il_version) l_reader.deserialize (l_eac_file.name, 0) if attached {like cache_content} l_reader.deserialized_object as l_cache_content then cache_content := l_cache_content else check deserialization_failed: not l_reader.successful end cache_content := Void end end end check_old_assemblies_in_cache -- Check if all guids from `old_assemblies' are still in the cache. require old_assemblies_not_void: old_assemblies /= Void cache_content_not_void: cache_content /= Void local l_guids: SEARCH_TABLE [READABLE_STRING_32] do -- build list of guids in cache if attached cache_content as c and then attached c.assemblies as cache_assemblies then across cache_assemblies as a from create l_guids.make (cache_assemblies.count) loop l_guids.force (a.unique_id) end else create l_guids.make (0) end if attached old_assemblies as a then across a as ic loop if not l_guids.has (ic.guid) then add_error (create {CONF_METADATA_CORRUPT}) end end end end is_cache_up_to_date (an_assembly: CONSUMED_ASSEMBLY): BOOLEAN -- Are the cache information for `an_assembly' up to date? -- Also set `cache_modified_date', require an_assembly_not_void: an_assembly /= Void local l_cache_mod_date, l_mod_date: INTEGER do if an_assembly.is_consumed then -- date of last modification to the assembly l_mod_date := file_path_modified_date (an_assembly.location) if l_mod_date = -1 then l_mod_date := file_path_modified_date (an_assembly.gac_path) end -- date of the cached information l_cache_mod_date := file_path_modified_date (full_cache_path.extended (an_assembly.folder_name).extended (types_info_file)) Result := l_mod_date /= -1 and then l_cache_mod_date > l_mod_date end end feature {NONE} -- Contract assemblies_valid: BOOLEAN -- Are `assemblies' valid? do if linear_assemblies.is_empty then Result := attached assemblies as a implies a.is_empty else Result := attached assemblies as a and then a.count = linear_assemblies.count and then across linear_assemblies as l all l.is_valid and l.classes_set and a.item (l.guid) = l end end end old_assemblies_valid: BOOLEAN -- Are `old_assemblies' valid? do Result := True if attached old_assemblies as a then Result := a.linear_representation.for_all (agent (a_assembly: CONF_PHYSICAL_ASSEMBLY_INTERFACE): BOOLEAN do Result := a_assembly.classes_set end) end end new_assemblies_valid: BOOLEAN -- Are `new_assemblies' valid? do Result := True if attached new_assemblies as l_assemblies then from l_assemblies.start until not Result or l_assemblies.after loop Result := l_assemblies.item_for_iteration.is_valid l_assemblies.forth end end end invariant factory_set: factory /= Void il_version_set: il_version /= Void and then not il_version.is_empty metadata_cache_path_set: metadata_cache_path /= Void linear_assemblies_not_void: linear_assemblies /= Void assemblies_valid: assemblies_valid old_assemblies_valid: old_assemblies_valid new_assemblies_valid: new_assemblies_valid added_classes_not_void: added_classes /= Void removed_classes_not_void: removed_classes /= Void modified_classes_not_void: modified_classes /= Void consume_assembly_observer_not_void: consume_assembly_observer /= Void note copyright: "Copyright (c) 1984-2023, Eiffel Software" license: "GPL version 2 (see http://www.eiffel.com/licensing/gpl.txt)" licensing_options: "http://www.eiffel.com/licensing" copying: "[ This file is part of Eiffel Software's Eiffel Development Environment. Eiffel Software's Eiffel Development Environment is free software; you can redistribute it and/or modify it under the terms of the GNU General Public License as published by the Free Software Foundation, version 2 of the License (available at the URL listed under "license" above). Eiffel Software's Eiffel Development Environment is distributed in the hope that it will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General Public License for more details. You should have received a copy of the GNU General Public License along with Eiffel Software's Eiffel Development Environment; if not, write to the Free Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA 02110-1301 USA ]" source: "[ Eiffel Software 5949 Hollister Ave., Goleta, CA 93117 USA Telephone 805-685-1006, Fax 805-685-6869 Website http://www.eiffel.com Customer support http://support.eiffel.com ]" end