note
	description: "Internal representation of the workbench."
	legal: "See notice at end of class."
	status: "See notice at end of class."
	date: "$Date$"
	revision: "$Revision$"

class WORKBENCH_I

inherit
	SHARED_ERROR_HANDLER
		export
			{ANY} Error_handler
		end

	SHARED_RESCUE_STATUS

	SHARED_FLAGS

	SHARED_DEGREES

	SHARED_COMPILATION_MODES

	SHARED_CONFIGURE_RESOURCES

	PROJECT_CONTEXT

	COMPILER_EXPORTER

	SHARED_EIFFEL_PROJECT

	CONF_ACCESS

	SYSTEM_CONSTANTS

	SHARED_NAMES_HEAP
		export
			{NONE} all
			{SYSTEM_I} names_heap
		end

	SHARED_EXECUTION_ENVIRONMENT

feature -- Status

	is_changed: BOOLEAN
			-- Have classes been changed?

feature -- Attributes

	universe: UNIVERSE_I
			-- Universe of the workbench

	system: SYSTEM_I
			-- Current system of the workbench

	lace: LACE_I
			-- Current lace description

	precompiled_directories: HASH_TABLE [REMOTE_PROJECT_DIRECTORY, INTEGER]
			-- Precompilation directories, indexed by precompilation ids

	precompiled_driver: PATH
			-- Full file name of the precompilation driver

	melted_file_name: detachable STRING
			-- File name of the melted file used by the precompilation driver.

	compilation_counter: INTEGER
			-- Number of recompilations

	backup_counter: INTEGER
			-- Number of recompilations using backups

	universe_defined: BOOLEAN
			-- Has the universe been defined?
		do
			Result := universe /= Void
		ensure
			defined: Result implies universe /= Void
		end

	system_defined: BOOLEAN
			-- Has the system been defined?
			-- (Yes, if the Ace file has been
			-- parsed).
		do
			Result := system /= Void
		ensure
			defined: Result implies system /= Void
		end

	successful: BOOLEAN
			-- Is the last compilation successful?
		do
			if system_defined then
				Result := lace.successful and then system.successful and not not_actions_successful
			end
		end

feature -- Status report: compilation

	compilation_status_without_complaint: like compilation_status = 0
			-- The status of a compilation finished without an issue.

	compilation_status_with_warning: like compilation_status = 1
			-- The status of a compilation finished without an error but with one or more warning.

	compilation_status_with_error: like compilation_status = 2
			-- The status of a compilation finished with one or more errors.

	is_compilation_status (v: like compilation_status): BOOLEAN
			-- Does `v` represent a valid compilation status value.
			-- See `compilation_status`.
		do
			Result :=
				(<<compilation_status_without_complaint,
				compilation_status_with_warning,
				compilation_status_with_error>>).has (v)
		ensure
			class
			definition: Result =
				(<<compilation_status_without_complaint,
				compilation_status_with_warning,
				compilation_status_with_error>>).has (v)
		end

	compilation_status: NATURAL_8
			-- Status of last compilation.
		do
			if not successful then
				Result := compilation_status_with_error
			elseif attached system as s and then s.has_warning then
				Result := compilation_status_with_warning
			else
				Result := compilation_status_without_complaint
			end
		ensure
			is_compilation_status (Result)
		end

feature -- Update

	set_lace (a_lace: like lace)
			-- Set `lace' to `a_lace'.
		require
			a_lace_not_void: a_lace /= Void
		do
			lace := a_lace
		ensure
			lace_set: lace = a_lace
		end

	set_changed
			-- Set `is_changed' to true.
		do
			is_changed := True
		ensure
			is_changed: is_changed
		end

	unset_changed
			-- Set `is_changed' to false.
		do
			is_changed := False
		ensure
			not_is_changed: not is_changed
		end

feature -- Update from retrieved object.

	update_from_retrieved_project (other: like Current)
			-- Update current object using fields of object attached
			-- to `other', so as to yield equal objects.
		require
			other_not_void: other /= Void
			lace_not_void: lace /= Void
		local
			l_lace: LACE_I
		do
			l_lace := lace
			standard_copy (other)
			lace := l_lace
			lace.update_from_retrieved_project (other.lace)
				-- Initialize SHARED_NAMES_HEAP with the heap of SYSTEM_I.
			set_names_heap (system.names)
		ensure
			lace_preserved: lace = old lace
		end

feature -- Additional properties

	is_already_compiled: BOOLEAN
			-- Has project already been compiled at least once?
		do
			Result := compilation_counter > 1
		end

	has_compilation_started: BOOLEAN
			-- Did Eiffel compilation already start in such a way that
			-- some option cannot be changed anymore.

	is_compiling: BOOLEAN
			-- Is the project being compiled?

	is_in_stable_state: BOOLEAN
			-- Is project in such a state that one can manipulate the project settings,
			-- add new classes, search for classes,...
		do
			Result := system_defined and then not is_compiling and then last_reached_degree <= 5
		ensure
			is_in_stable_state_implies_ready: Result implies is_universe_ready
		end

	is_universe_ready: BOOLEAN
			-- Is project in such a state that one can search for classes/clusters/libraries.
		do
			Result := system_defined and then last_reached_degree <= 5
		end

	last_reached_degree: INTEGER
			-- What is the lowest degree that was reached during last compilation?
			-- Or what is the current degree if `is_compiling'?
			-- Warning: it might not have been completed!
		do
			if not successful then
				Result := Eiffel_project.degree_output.last_degree
			else
				Result := -4
			end
		end

feature -- Conveniences

	set_system (s: like system)
			-- Assign `s' to `system'.
		require
			valid_s: s /= Void
			valid_heap: s.names /= Void
		do
			system := s
				-- Initialize SHARED_NAMES_HEAP with the heap of SYSTEM_I.
			set_names_heap (system.names)
		ensure
			system_set: system = s
			names_heap_set: names_heap = system.names
		end

	set_compilation_started
			-- Assign `True' to `has_compilation_started'.
		do
			has_compilation_started := True
		ensure
			has_compilation_started_set: has_compilation_started
		end

	set_precompiled_directories (directories: like precompiled_directories)
			-- Set `precompiled_directories' to `directories'.
		require
			directories_not_void: directories /= Void
		do
			precompiled_directories := directories
		ensure
			assigned: precompiled_directories = directories
		end

	set_precompiled_driver (pd: like precompiled_driver)
			-- Set `precompiled_driver' to `pd'.
		do
			precompiled_driver := pd
		ensure
			assigned: precompiled_driver = pd
		end

	set_melted_file_name (update_file_name: like melted_file_name)
			-- Set `melted_file_name' to `upate_file_name'.
		do
			if update_file_name /= Void and then not update_file_name.is_empty then
				melted_file_name := update_file_name.twin
			else
				melted_file_name := Void
			end
		ensure
			assigned: melted_file_name /= Void implies equal (melted_file_name, update_file_name)
			reset: melted_file_name = Void implies (update_file_name = Void or else update_file_name.is_empty)
		end

	on_project_loaded
			-- A project has just been loaded.
			-- Initialize `Current' accordingly.
		do
			is_compiling := False
			forbid_degree_6 := False
		end

feature -- Initialization

	make
			-- Initialize the workbench.
			-- (Do not create system until the
			-- first compilation).
		do
			if universe = Void then
				create universe.make
			end
			if lace = Void then
				create lace.make
			end
			create precompiled_directories.make (5)

			compilation_counter := 1
			backup_counter := 0
			new_session := True
		ensure
			initialized: universe /= Void and then lace /= Void
		end

feature -- Commands

	process_actions (an_actions: ARRAYED_LIST [CONF_ACTION])
			-- Process pre/post compilation actions.
		require
			an_actions_not_void: an_actions /= Void
		local
			l_action: CONF_ACTION
			vd85: VD85
			l_prc_factory:  BASE_PROCESS_FACTORY
			l_prc_launcher: BASE_PROCESS
			l_success: BOOLEAN
			l_wd: detachable IMMUTABLE_STRING_32
			l_state: CONF_STATE
		do
			create l_prc_factory
			l_state := universe.conf_state_from_target (lace.target)
			from
				an_actions.start
			until
				an_actions.after
			loop
				l_action := an_actions.item
				if l_action.is_enabled (l_state) then
					if attached l_action.working_directory as l_working_directory then
						l_wd := l_working_directory.evaluated_path.name
					else
						l_wd := Void
					end
					l_prc_launcher :=
						if {PLATFORM}.is_windows then
							if attached execution_environment.item ("COMSPEC") as l_comspec then
								l_prc_factory.process_launcher_with_command_line (l_comspec + {STRING_32} " /C " + l_action.command, l_wd)
							else
								l_prc_factory.process_launcher_with_command_line (l_action.command, l_wd)
							end
						else
							l_prc_factory.process_launcher
								("/bin/sh",
								<<{STRING_32} "-c", {STRING_32} "%'%'" + l_action.command + "%'%'">>,
								l_wd)
						end
					l_prc_launcher.set_separate_console (is_gui)

					l_prc_launcher.launch
					if l_prc_launcher.launched then
						l_prc_launcher.wait_for_exit
						l_success := l_prc_launcher.exit_code = 0
					end
					if not l_success then
						if l_action.must_succeed then
							not_actions_successful := True
							error_handler.insert_error (create {VD84}.make (l_action.command))
							error_handler.checksum
						else
							create vd85.make (l_action.command)
							error_handler.insert_warning (vd85, lace.target.options.is_warning_as_error)
						end
					end
				end
				an_actions.forth
			end
		end

	recompile (is_for_finalization, a_syntax_analysis, a_system_check, a_generate_code: BOOLEAN)
			-- Incremental recompilation
		local
			retried: INTEGER
			missing_class_error: BOOLEAN
			degree_6_done: BOOLEAN
			l_pre_actions_done: BOOLEAN
		do
			if retried = 0 then
				error_handler.clear_display
				not_actions_successful := False
			end
			if retried = 0 and then (system = Void or else system.automatic_backup) then
					-- Even if backup is not enabled, we will always create a BACKUP
					-- directory. This will be done only once if `automatic_backup' is not
					-- enabled.
				backup_counter := backup_counter + 1
				create_backup_directory
				save_starting_backup_info
			end

				-- We perform a degree 6 only when it is the first the compilation or
				-- when there was an error during the compilation concerning a missing
				-- class and that no degree 6 has been done before.
				-- To avoid a recursion, we do it at most twice.
			if
				not degree_6_done and then
				(retried = 0 or else (retried = 1 and then missing_class_error))
			then
				if not forbid_degree_6 then
					Lace.recompile
				end

				if Lace.successful then
					if not l_pre_actions_done then
						process_actions (universe.conf_system.all_pre_compile_action)
						l_pre_actions_done := True
					end

					System.set_rebuild (False)
					if Lace.has_changed then
						System.set_config_changed (True)
						System.set_melt
						System.set_finalize
					else
						System.set_config_changed (False)
					end
					if Lace.has_group_changed or missing_class_error or compilation_modes.is_discover then
						system.set_rebuild (True)
					end
					System.recompile (is_for_finalization, a_syntax_analysis, a_system_check, a_generate_code)

					process_actions (universe.conf_system.all_post_compile_action)
				else
					if not Error_handler.error_list.is_empty then
						Error_handler.raise_error
					end
				end

				if successful then
					unset_changed
					system.reset_has_potential_class_name_mismatch
					system.set_rebuild (False)
					system.reset_has_compilation_started
					compilation_counter := compilation_counter + 1
					if System.has_been_changed or else System.freezing_occurred then
						save_project (Compilation_modes.is_precompiling)
					end
				end

					-- If the compilation is successful we are going to print the
					-- warnings only. If there was an error during the compilation,
					-- this feature won't never be called and the Error_handler.trace
					-- from the rescue clause will print the warnings
				Error_handler.trace_warnings
			else
				retried := 2
			end

				--| Store the System info even after an error
				--|	(the next compilation will be stored in a different
				--| directory)
			if system /= Void and then system.automatic_backup then
				save_ending_backup_info
			end
		ensure
			increment_compilation_counter:
				(successful and (System.has_been_changed or else System.freezing_occurred))
					implies compilation_counter = old compilation_counter + 1
			error_handler_empty: error_handler.error_list.is_empty
		rescue
			if Rescue_status.is_error_exception then
				Error_handler.force_display
				if error_handler.has_error_of_type ({VIGE}) then
						-- An error occurs during IL generation, we need to
						-- save current project otherwise EIFGEN is corrupted
						-- due to a bad project file. We also increment
						-- `compilation_counter' since even though it is not
						-- successful, project is created.
					compilation_counter := compilation_counter + 1
					save_project (Compilation_modes.is_precompiling)
				end
				Rescue_status.set_is_error_exception (False)
				retried := retried + 1
				if not missing_class_error then
					degree_6_done := system_defined and then system.is_rebuild and then not system.has_potential_class_name_mismatch
					if
						not degree_6_done and then
						(error_handler.has_error_of_type ({VTCT}) or
						error_handler.has_error_of_type ({VD71}) or
						error_handler.has_error_of_type ({VD29}) or
						error_handler.has_error_of_type ({VD21}) or
						error_handler.has_error_of_type ({VD20}) or
						error_handler.has_error_of_type ({VSCN}) or
						(attached {INTERNAL_ERROR} error_handler.first_error_of_type ({INTERNAL_ERROR}) as l_int and then l_int.is_class_name_mismatch))
					then
						missing_class_error := True
						lace.reset_date_stamp
						Error_handler.wipe_out
							-- We are trying to find a missing class.
							-- This can be done only once, because `missing_class_error' is now set.
							-- Therefore we have to do our best to figure out whether it is actually present.
							-- That's why we force the compiler to look into the source code of new files
							-- instead of using their names to guess the associated class name.
						system.set_has_potential_class_name_mismatch
					else
						Error_handler.trace
					end
				else
					missing_class_error := False
					Error_handler.trace
				end
				if system_defined then
						-- System is created if precompilation is valid
					System.set_current_class (Void)
				end
				retry
			else
				stop_compilation
			end
		end

	save_project (was_precompiling: BOOLEAN)
			-- Save project after a successful compilation.
		do
				-- FIXME: We don't purge the system when precompiling, because of a
				-- problems with IDs and we give a `False' arguments to
				-- `prepare_before_saving' when precompiling.
				-- i.e. the system which is using the precompiled can think that some
				-- IDs are available but they are not. This is due because of a bad
				-- merging of SERVER_CONTROLs from the different precompiled libraries.
			System.prepare_before_saving (not was_precompiling)
			if was_precompiling then
				System.save_precompilation_info
			end
				-- NOTE: possible speed improvement by saving the project when this
				-- condition is satisfied:
				-- not (Compilation_modes.is_quick_melt and then not freezing_occurred)
				-- The drawback is that if you crash and did not save your project
				-- is corrupted.
			Eiffel_project.save_project
			if was_precompiling then
				Eiffel_project.save_precomp
			end
		end

	change_class (cl: CLASS_I)
			-- Change a class of the system.
		require
			good_argument: cl /= Void
			not_override: not cl.config_class.does_override
		do
			add_class_to_recompile (cl)

				-- Mark the class syntactically changed
			cl.set_changed (True)

				-- Syntax analysis must be done
			Degree_5.insert_new_class (cl.compiled_class)
		end

	change_all_new_classes
			-- Record all the classes in the universe as
			-- changed (for compilation using `ANY' as root class)
		local
			classes: SEARCH_TABLE [CLASS_I]
			cl: CLASS_I
		do
			from
				classes := universe.all_classes
				classes.start
			until
				classes.after
			loop
				cl := classes.item_for_iteration
				if not cl.is_compiled and not cl.is_external_class and not cl.config_class.does_override then
					change_class (cl)
				end
				classes.forth
			end
		end

	add_class_to_recompile (cl: CLASS_I)
			-- Recompile the class but do not do the parsing
		require
			good_argument: cl /= Void
		local
			class_to_recompile: CLASS_C
		do
			class_to_recompile := cl.compiled_class
			if class_to_recompile = Void then
					-- Creation of a new instance of a class to recompile:
				   -- a class neither compiled must be compiled.
				class_to_recompile := cl.class_to_recompile
					-- Update system
				system.insert_new_class (class_to_recompile)
			end

				-- Insertion in the pass controlers
			Degree_5.insert_class (class_to_recompile)
			Degree_4.insert_new_class (class_to_recompile)
			Degree_3.insert_new_class (class_to_recompile)
			Degree_2.insert_new_class (class_to_recompile)
		end

feature -- Directory creation

	create_data_directory
			-- Create the subdirectory for data storage.
		local
			d: DIRECTORY
		do
			if universe_defined then
				create d.make_with_path (project_location.data_path)
				if not d.exists then
					d.create_dir
				end
			end
		end

feature -- Automatic backup

	create_backup_directory
			-- Create the subdirectory for backup `backup_counter'
		local
			d: DIRECTORY
		do
				-- Create the EIFGEN/BACKUP directory
			create d.make_with_path (project_location.backup_path)
			if not d.exists then
				d.create_dir
			end

				-- Create the EIFGEN/BACKUP/COMP<n> directory
			create d.make_with_path (backup_subdirectory)
			if not d.exists then
				d.create_dir
			end
		end

	backup_subdirectory: PATH
			-- Current backup subdirectory
		local
			temp: STRING_32
		do
			create temp.make (9)
			temp.append_string_general (Comp)
			temp.append_integer (backup_counter)
			Result := project_location.backup_path.extended (temp)
		end

	backup_info_file_name: PATH
			-- File where info about the compilation is saved
		do
			Result := backup_subdirectory.extended (backup_info)
		end

	save_starting_backup_info
			-- Save the information about this compilation
		local
			file: PLAIN_TEXT_FILE
			u: UTF_CONVERTER
		do
			create file.make_with_path (backup_info_file_name)
			if file.is_creatable or else (file.exists and then file.is_writable) then
				file.open_append
				file.put_string ("Compiler version: ")
				file.put_string (u.utf_32_string_to_utf_8_string_8 (Version_number))
				file.put_new_line
				file.put_string ("Type: ")
				file.put_string (compilation_modes.string_representation)
				file.put_new_line
				file.put_string ("batch mode: ")
				file.put_boolean (not is_gui)
				file.put_new_line
				file.put_string ("new session: ")
				file.put_boolean (new_session)
				file.put_new_line
				file.put_string ("starting date: ")
				file.put_string ((create {DATE_TIME}.make_now).out)
				file.put_new_line
				file.close
			end

			new_session := False
		end

	save_ending_backup_info
			-- Save the information about the status of this compilation
		local
			file: PLAIN_TEXT_FILE
		do
			create file.make_with_path (backup_info_file_name)
			if file.is_creatable or else (file.exists and then file.is_writable) then
				file.open_append
				file.put_string ("Compilation status is: ")
				file.put_boolean (successful)
				file.put_new_line
				file.put_string ("ending date: ")
				file.put_string ((create {DATE_TIME}.make_now).out)
				file.put_new_line
				file.close
			end

			new_session := False
		end

feature {E_PROJECT} -- Status update

	start_compilation
			-- Warn the interface that a new compilation is beginning.
		do
			is_compiling := True
			Eiffel_universe.reset_internals
			Eiffel_project.Manager.on_project_compiles
		end

	stop_compilation
			-- Warn the interface that a compilation is over.
		do
			is_compiling := False
			if eiffel_project.manager.is_created then
				Eiffel_project.Manager.on_project_recompiled (compilation_status)
			end
		end

feature {NONE} -- Automatic Backup

	new_session: BOOLEAN
			-- Is it the first compilation in the session?

	forbid_degree_6: BOOLEAN
			-- Should the next compilation avoid to perform a degree 6?

feature {NONE} -- Implementation

	not_actions_successful: BOOLEAN;
			-- Was there a problem during running the pre and post compile actions?

note
	copyright:	"Copyright (c) 1984-2020, 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