note

	description:

		"Geant commands"

	library: "Gobo Eiffel Ant"
	copyright: "Copyright (c) 2001-2018, Sven Ehrke and others"
	license: "MIT License"
	date: "$Date$"
	revision: "$Revision$"

class GEANT_GEANT_COMMAND

inherit

	GEANT_COMMAND
		redefine
			make
		end

create

	make

feature {NONE} -- Initialization

	make (a_project: GEANT_PROJECT)
			-- Initialize command by setting `project' to `a_project'.
		do
			precursor (a_project)
				-- Create actual arguments:
			create arguments.make
		ensure then
			arguments_not_void: arguments /= Void
		end

feature -- Access

	filename: detachable STRING
			-- Geant build file to invoke

	fileset: detachable GEANT_FILESET
			-- Fileset for current command

	reuse_variables: BOOLEAN
			-- Are variables reused in new project?

	start_target_name: detachable STRING
			-- Name of target the build process starts with

	arguments: GEANT_ARGUMENT_VARIABLES
			-- Actual arguments

	exit_code_variable_name: detachable STRING
			-- Name of variable holding exit code of execution

feature -- Status report

	is_filename_executable: BOOLEAN
			-- Can command be executed on a project file?
		do
			Result := attached filename as l_filename and then l_filename.count > 0
		ensure
			filename_not_void_and_not_empty: Result implies attached filename as l_filename and then l_filename.count > 0
		end

	is_target_executable: BOOLEAN
			-- Can command be executed on a target?
		do
			Result := attached start_target_name as l_start_target_name and then l_start_target_name.count > 0
		ensure
			target_not_void_and_not_empty: Result implies attached start_target_name as l_start_target_name and then l_start_target_name.count > 0
		end

	is_fileset_executable: BOOLEAN
			-- Can command be executed on fileset `fileset'?
		do
			Result := fileset /= Void
		ensure
			fileset_not_void: Result implies fileset /= Void
		end

	is_executable: BOOLEAN
			-- Can command be executed?
		do
			Result := is_filename_executable or is_target_executable
		ensure then
			project_or_target: Result implies is_filename_executable or is_target_executable
		end

	fork: BOOLEAN
			-- Should a new process be spawned to execute command?

feature -- Setting

	set_filename (a_filename: like filename)
			-- Set `filename' to `a_filename'.
		require
			a_filename_not_void: a_filename /= Void
			a_filename_not_empty: a_filename.count > 0
			-- file_exists:
		do
			filename := a_filename
		ensure
			filename_set: filename = a_filename
		end

	set_fileset (a_fileset: like fileset)
			-- Set `fileset' to `a_fileset'.
		require
			a_fileset_not_void: a_fileset /= Void
		do
			fileset := a_fileset
		ensure
			fileset_set: fileset = a_fileset
		end

	set_reuse_variables(a_reuse_variables: BOOLEAN)
			-- Set `reuse_variables' to `a_reuse_variables'
		do
			reuse_variables := a_reuse_variables
		ensure
			reuse_variables_set: reuse_variables = a_reuse_variables
		end

	set_fork (a_fork: BOOLEAN)
			-- Set `fork' to `a_fork'
		do
			fork := a_fork
			has_fork_been_set := True
		ensure
			fork_set: fork = a_fork and has_fork_been_set
		end

	set_start_target_name (a_start_target_name: like start_target_name)
			-- Set `start_target_name' to `a_start_target_name'.
		require
			a_start_target_name_not_void: a_start_target_name /= Void
			a_start_target_name_not_empty: a_start_target_name.count > 0
			-- file_exists:
		do
			start_target_name := a_start_target_name
		ensure
			start_target_name_set: start_target_name = a_start_target_name
		end

	set_exit_code_variable_name (a_exit_code_variable_name: like exit_code_variable_name)
			-- Set `exit_code_variable_name' to `a_exit_code_variable_name'.
		require
			a_exit_code_variable_name_not_void: a_exit_code_variable_name /= Void
			a_exit_code_variable_name_not_empty: a_exit_code_variable_name.count > 0
		do
			exit_code_variable_name := a_exit_code_variable_name
		ensure
			exit_code_set: exit_code_variable_name = a_exit_code_variable_name
		end

feature -- Execution

	execute
			-- Execute command.
		local
			a_filename: STRING
			a_fork: BOOLEAN
		do
			exit_code := 0
			if is_filename_executable and then attached filename as l_filename then
				if has_fork_been_set then
					a_fork := fork
				else
						-- Set default value for fork:
					a_fork := True
				end
					-- Create a new project and run it's build process:
				a_filename := file_system.pathname_from_file_system (l_filename, unix_file_system)
				if a_fork then
					execute_forked_with_filename_and_target (a_filename, start_target_name)
				else
					execute_with_filename (a_filename)
				end
			else
				check target_executable: attached start_target_name as l_start_target_name then
					if has_fork_been_set then
						a_fork := fork
					else
							-- Set default value for fork:
						a_fork := False
					end
						-- Call target of current project:
					if a_fork then
						execute_forked_with_target (l_start_target_name)
					else
						execute_with_target (l_start_target_name)
					end
				end
			end

			if attached exit_code_variable_name as l_exit_code_variable_name then
					-- Store return_code of execution:
				project.set_variable_value (l_exit_code_variable_name, exit_code.out)
					-- Reset `exit_code' since return code of execution is available through
					-- variable 'exit_code_variable_name':
				exit_code := 0
			end

		end

feature {NONE} -- Implementation

	execute_forked_with_filename_and_target (a_filename: STRING; a_target_name: detachable STRING)
			-- Spawn new geant process to execute scriptfile named `a_filename';
			-- If `a_target_name' is not Void and not empty pass it as start target name.
			-- TODO: support filesets
		local
			cmd: STRING
			a_level: STRING
		do
			a_level := ""
			if a_target_name /= Void and then a_target_name.count > 0 then
				project.trace_debug (<<"  [*geant] execute_forked_with_filename_and_target: '", a_filename, "', '", a_target_name, "'">>)
			else
				project.trace_debug (<<"  [*geant] execute_forked_with_filename_and_target: '", a_filename, "'">>)
			end
			if project.options.debug_mode then
				if Project_variables_resolver.has ("geant.geant.level") and then attached Project_variables_resolver.value ("geant.geant.level") as l_geant_geant_level then
					a_level := STRING_.concat (l_geant_geant_level, "#")
				else
					project.trace_debug (<<"  [*geant] no variable 'geant.geant.level' found">>)
					a_level := "#"
				end
				project.trace (<<"  [geant] geant.geant.level=", a_level>>)
				project.variables.put (a_level, "geant.geant.level")
				if a_level.count > 25 then
					exit_application (1, <<"TOO MANY RECURSIVE FORKED GEANT CALLS">>)
				end
			end
			create cmd.make (256)
			cmd.append_string ("geant")
			cmd := STRING_.appended_string (cmd, options_and_arguments_for_cmdline)
			if project.options.debug_mode then
				cmd.append_string (" -Dgeant.geant.level=")
				cmd := STRING_.appended_string (cmd, a_level)
			end
				-- Pass name of buildscript:
			cmd.append_string (" -b ")
			cmd := STRING_.appended_string (cmd, a_filename)
				-- Pass name of starttarget:
			if a_target_name /= Void and then a_target_name.count > 0 then
				cmd.append_string (" ")
				cmd := STRING_.appended_string (cmd, a_target_name)
			end
			project.trace (<<"  [geant] ", cmd>>)
			execute_shell (cmd)
		end

	execute_forked_with_target (a_target_name: STRING)
			-- Spawn new geant process for current buildscript and execute target named `a_target_name'.
			-- TODO: support filesets
		local
			a_filename: STRING
			a_key: STRING
		do
			project.trace_debug (<<"  [*geant] execute_forked_with_target: '", a_target_name, "'">>)
			if is_fileset_executable then
				--TODO
			else
					-- Determine name of buildfile of current build script:
--				a_key := STRING_.concat (project.name, ".absdir")
--				a_filename := project.variables.item (a_key)
--TODO:				a_filename := STRING_.concat (a_filename, project.variables.item ("path_separator"))
--				a_filename := STRING_.concat (a_filename, "/")
				a_key := STRING_.concat (project.name, ".filename")
--				a_filename := STRING_.concat (a_filename, project.variables.item (a_key))
				a_filename := project.variables.item (a_key)
				a_filename := file_system.pathname_from_file_system (a_filename, unix_file_system)
				execute_forked_with_filename_and_target (a_filename, a_target_name)
			end
		end

	execute_with_filename (a_filename: STRING)
			-- Create new project for scriptfile named `a_filename' and run it's build process.
		local
			a_project_loader: GEANT_PROJECT_LOADER
			a_project: GEANT_PROJECT
			a_target: GEANT_TARGET
			a_variables: GEANT_PROJECT_VARIABLES
		do
			if reuse_variables then
				a_variables := project.variables
			else
				create a_variables.make
			end
			create a_project_loader.make (a_filename)
			a_project_loader.load (a_variables, project.options)
			if attached a_project_loader.project_element as l_project_element then
				a_project := l_project_element.project
				a_project.merge_in_parent_projects

					-- Load build configuration:
				if attached start_target_name as l_start_target_name and then l_start_target_name.count > 0 then
					if not attached a_project.targets as l_project_targets or else not l_project_targets.has (l_start_target_name) then
						exit_application (1, <<"Project '", a_project.name,
							"' does not contain a target named `", l_start_target_name + "%'">>)
					else
							-- Check export status of target to be called:
						a_target := l_project_targets.item (l_start_target_name)
						if not (a_target.is_exported_to_any or else
							a_target.is_exported_to_project (project)) then
							exit_application (1, <<"target: `", a_target.full_name,
								"%' is not exported to project '", project.name, "'">>)
						end

						a_project.set_start_target_name (l_start_target_name)
					end
				end
					-- Start build process:
				if exit_code = 0 then
					if is_fileset_executable and then attached fileset as l_fileset then
						if not l_fileset.is_executable then
							project.log (<<"  [geant] error: fileset definition wrong">>)
							exit_code := 1
						end
						if exit_code = 0 then
							l_fileset.execute
							from l_fileset.start until l_fileset.after or else exit_code /= 0 loop
								a_target := a_project.start_target
								a_project.build (arguments)
								if not a_project.build_successful then
									exit_code := 1
								end

								l_fileset.forth
							end
						end
					else
						a_target := a_project.start_target
						a_project.build (arguments)
						if not a_project.build_successful then
							exit_code := 1
						end
					end
				end
			else
				exit_code := 1
			end
		end

	execute_with_target (a_target_name: STRING)
			-- Call target named `a_target_name' of current project.
		require
			target_executable: is_target_executable
		local
			a_target: GEANT_TARGET
		do
			check precondition: attached start_target_name as l_start_target_name then
				if attached project.targets as l_project_targets and then l_project_targets.has (l_start_target_name) then
					if is_fileset_executable and then attached fileset as l_fileset then
						if not l_fileset.is_executable then
							project.log (<<"  [geant] error: fileset definition wrong">>)
							exit_code := 1
						end
						if exit_code = 0 then
							l_fileset.execute
							a_target := l_project_targets.item (l_start_target_name)
							a_target := a_target.final_target
							from l_fileset.start until l_fileset.after or else exit_code /= 0 loop
								a_target.project.build_target (a_target, arguments)
								l_fileset.forth
							end
						end
					else
						a_target := l_project_targets.item (l_start_target_name)
						a_target := a_target.final_target
						a_target.project.build_target (a_target, arguments)
					end
				else
					project.log (<<"  [geant] error: unknown target: `", l_start_target_name, "%'">>)
					exit_code := 1
				end
			end
		end

	options_and_arguments_for_cmdline: STRING
			-- All options and arguments (without built-in ones) as STRINGs for
			-- geant commandline call
		local
			cs: DS_HASH_TABLE_CURSOR [STRING, STRING]
		do
			create Result.make (128)
				-- Pass options:
			if project.options.verbose then
				Result.append_string (" -v")
			end
			if project.options.debug_mode then
				Result.append_string (" -d")
			end
			if project.options.no_exec then
				Result.append_string (" -n")
			end
				-- Pass arguments:
			cs := arguments.new_cursor
			from cs.start until cs.after loop
				project.trace_debug (<<"    [*geant] variable: ", cs.key, "=", cs.item>>)
				Result := STRING_.appended_string (Result, " -A%"")
				Result := STRING_.appended_string (Result, cs.key)
				Result := STRING_.appended_string (Result, "=")
				Result := STRING_.appended_string (Result, cs.item)
				Result := STRING_.appended_string (Result, "%"")
				cs.forth
			end
		ensure
			options_and_arguments_for_cmdline_not_void: options_and_arguments_for_cmdline /= Void
		end

	has_fork_been_set: BOOLEAN
			-- Has attribute `fork' been set?

end