rule Copy
{
	if $(2) {
		SEARCH on $(2) += $(SEARCH_SOURCE) ;
		Depends $(1) : <build>copyattr $(2) ;
		Copy1 $(1) : <build>copyattr $(2) ;
	}
}


actions Copy1
{
	$(HOST_ADD_BUILD_COMPATIBILITY_LIB_DIR)
	"$(2[1])" -d "$(2[2-])" "$(1)"
}


rule SymLink
{
	# SymLink <target> : <source> : <makeDefaultDependencies> ;
	# Links <target> to <source>.
	# <source> is the exact link contents. No binding is done.
	# <makeDefaultDependencies> If true, <target> will be made a dependency
	# of the `all' pseudo target, i.e. it will be made by default, and removed
	# on `jam clean'.

	local target = $(1) ;
	local source = $(2) ;
	local makeDefaultDependencies = $(3) ;
	if ! $(makeDefaultDependencies) {
		makeDefaultDependencies = true ;
	}
	LINKCONTENTS on $(target) = $(source) ;
	SymLink1 $(target) ;
	if $(makeDefaultDependencies) = true {
		LocalDepends files : $(target) ;
		LocalClean clean : $(target) ;
	}
}

actions SymLink1
{
	$(RM) "$(1)" && $(LN) -s "$(LINKCONTENTS)" "$(1)"
}

rule RelSymLink
{
	# RelSymLink <link> : <link target> : <makeDefaultDependencies> ;
	# Creates a relative symbolic link from <link> to <link target>.
	# <link> and <link target> can be usual targets. They may have a grist
	# and don't need to have any dirname. Their LOCATE variables are used to
	# find their locations.
	# <makeDefaultDependencies> If true (which is the default), <link> will be
	# made a dependency of the `files' pseudo target, i.e. it will be made by
	# default, and removed on `jam clean'.

	local target = $(1) ;
	local source = $(2) ;
	local makeDefaultDependencies = $(3) ;
	local targetDir = [ on $(target) FDirName $(LOCATE[1]) $(target:D) ] ;
	local sourceDir = [ on $(source) FDirName $(LOCATE[1]) $(source:D) ] ;
	local sourcePath = $(source:G=) ;
	sourcePath = $(sourcePath:D=$(sourceDir)) ;
	local targetDirComponents = [ FSplitPath $(targetDir) ] ;
	local sourceComponents = [ FSplitPath $(sourcePath) ] ;

	SymLink $(target)
		: [ FRelPath $(targetDirComponents) : $(sourceComponents) ]
		: $(makeDefaultDependencies) ;
	NOUPDATE $(target) ;
	Depends $(target) : $(source) ;
}

rule AbsSymLink
{
	# AbsSymLink <link> : <link target> : <link dir>
	#			: <makeDefaultDependencies> ;
	# Creates an absolute symbolic link from <link> to <link target>.
	# <link> and <link target> must be usual targets. If <link dir> is
	# given, then it is set as LOCATE directory on <link>.
	# <makeDefaultDependencies> If true (which is the default), <link> will be
	# made a dependency of the `files' pseudo target, i.e. it will be made by
	# default, and removed on `jam clean'.

	local makeDefaultDependencies = $(4) ;
	if ! $(makeDefaultDependencies) {
		makeDefaultDependencies = true ;
	}

	Depends $(1) : $(2) ;
	if $(3) {
		MakeLocate $(1) : $(3) ;
	}
	SEARCH on $(2) += $(SEARCH_SOURCE) ;
	if $(makeDefaultDependencies) = true {
		LocalDepends files : $(1) ;
		LocalClean clean : $(1) ;
	}
}

actions AbsSymLink
{
	target="$(2)"
	case "$target" in
		/*) ;;
		*) target=`pwd`/"$target";;
	esac
	$(RM) "$(1)" && $(LN) -s "$target" "$(1)"
}

rule HaikuInstall installAndUninstall : dir : sources : installgrist
	: installRule : targets
{
	# Usage: HaikuInstall <[ install [ and uninstall ] pseudotarget ]>
	#	: <directory> : <sources to install> : [ <installgrist> ]
	#	: [ <install rule> ] : [ <targets> ] ;

	local install = $(installAndUninstall[1]) ;
	install ?= install ;
	local uninstall = $(installAndUninstall[2]) ;
	uninstall ?= un$(install) ;
	installgrist ?= $(INSTALLGRIST) ;
	installRule ?= Install ;

	targets ?= $(sources) ;
	targets = $(targets:G=$(installgrist)) ;

	NotFile $(install) ;
	NotFile $(uninstall) ;
	Depends $(install) : $(targets) ;
	Clean $(uninstall) : $(targets) ;

	SEARCH on $(sources) += $(SEARCH_SOURCE) ;
	MakeLocate $(targets) : $(dir) ;

	local source ;
	for source in $(sources) {
		local target = $(targets[1]) ;
		targets = $(targets[2-]) ;

		Depends $(target) : $(source) ;
		$(installRule) $(target) : $(source) ;

		if [ on $(target) return $(MODE) ] {
			Chmod $(target) ;
		}

		if $(OWNER) && $(CHOWN) {
			Chown $(target) ;
			OWNER on $(target) = $(OWNER) ;
		}

		if $(GROUP) && $(CHGRP) {
			Chgrp $(target) ;
			GROUP on $(target) = $(GROUP) ;
		}
	}
}

rule InstallAbsSymLinkAdapter
{
	# InstallAbsSymLinkAdapter <link> : <link target>
	if ! [ on $(2) return $(TARGET) ] {
		TARGET on $(2) = [ on $(2) return $(SEARCH) ] ;
	}
	AbsSymLink $(1) : $(2) : : false ;
}

rule HaikuInstallAbsSymLink
{
	# Usage: HaikuInstallAbsSymLink <[ install [ and uninstall ] pseudotarget ]>
	#							   : <directory> : <sources to install>
	#							   : [ <installgrist> ] ;
	HaikuInstall $(1) : $(2) : $(3) : $(4) : InstallAbsSymLinkAdapter ;
}

rule InstallRelSymLinkAdapter
{
	# InstallRelSymLinkAdapter <link> : <link target>
	if ! [ on $(2) return $(TARGET) ] {
		TARGET on $(2) = [ on $(2) return $(SEARCH) ] ;
	}
	RelSymLink $(1) : $(2) : false ;
}

rule HaikuInstallRelSymLink
{
	# Usage: HaikuInstallRelSymLink <[ install [ and uninstall ] pseudotarget ]>
	#							   : <directory> : <sources to install>
	#							   : [ <installgrist> ] ;
	HaikuInstall $(1) : $(2) : $(3) : $(4) : InstallRelSymLinkAdapter ;
}


rule UnarchiveObjects
{
	# UnarchiveObjects <target objects> : <static object>

	MakeLocateArch $(1) ;
	Depends $(1) : $(2) ;
	SEARCH on $(2) = $(SEARCH_SOURCE) ;
}

actions UnarchiveObjects
{
	( cd $(1[1]:D) && $(TARGET_AR_$(TARGET_PACKAGING_ARCH)) \
		$(TARGET_UNARFLAGS_$(TARGET_PACKAGING_ARCH)) "$(2)" $(1:BS) )
}


rule ExtractArchive directory : entries : archiveFile : grist
{
	# ExtractArchive <directory> : <entries> : <archiveFile> [ : <grist> ]
	#
	# Extract the archive file target <archiveFile> to directory <directory>.
	# The rule can be called multiple times for different <entries> for the same
	# <directory> and <archiveFile> combo.
	#
	# <directory> - The directory into which to extract the archive file. The
	#               directory is created by this rule and it is the target
	#               that the extract action is associated with.
	# <entries>   - The entries of the archive file one is interested in. The
	#               rule always extracts the complete archive file, from the
	#               given entries the rule creates targets (using <grist>)
	#               representing the extracted entries. Those targets are
	#               returned by the rule.
	# <archiveFile> - The archive file target to extract.
	# <grist>     - The grist used to create targets from <entries>. Defaults to
	#               "extracted".

	grist ?= extracted ;

	# Turn the entries into targets to build.
	local targets ;
	local entry ;
	for entry in $(entries) {
		local target = $(entry:G=$(grist)) ;
		targets += $(target) ;
	}

	LOCATE on $(targets) = $(directory:G=) ;
	Depends $(targets) : $(directory) $(archiveFile) ;
	NoUpdate $(targets) ;

	# one-time initialization for the main target (the directory)
	if ! [ on $(directory) return $(INITIALIZED) ] {
		# make sure the parent dir exists
		local parentDir = $(directory:PG=dir) ;
		Depends $(directory) : $(parentDir) ;
		MkDir $(parentDir) ;

		NoUpdate $(directory) ;
		Depends $(directory) : $(archiveFile) ;
		switch $(archiveFile:S)
		{
			case .zip :
				ExtractZipArchive1 $(directory) : $(archiveFile) ;

			case .tgz :
				ExtractTarArchive1 $(directory) : $(archiveFile) ;

			case .hpkg :
				Depends $(directory) : <build>package ;
				ExtractHPKGArchive1 $(directory)
					: <build>package $(archiveFile) ;

			case "" :
				Exit "ExtractArchive: No archive passed" ;

			case * :
				Exit "ExtractArchive: Unhandled archive extension:"
					"$(archiveFile:S)" ;
		}
		INITIALIZED on $(directory) = 1 ;
	}

	return $(targets) ;
}


actions ExtractZipArchive1
{
	mkdir -p $(1)
	unzip -q -u -o -d $(1) $(2)
}


actions ExtractTarArchive1
{
	mkdir -p $(1)
	tar -C $(1) -xf $(2)
}


actions ExtractHPKGArchive1
{
	mkdir -p "$(1)"
	$(HOST_ADD_BUILD_COMPATIBILITY_LIB_DIR)
	$(2[1]) extract -C "$(1)" "$(2[2])"
}


rule ObjectReference
{
	# ObjectReference <reference object> : <source object>
	# Makes <reference object> refer to the same file as <source object>.
	# The filenames must of course be identical.
	# <source object> must have already been LOCATEd.

	local ref = $(1) ;
	local source = $(2) ;
	if $(ref) != $(source) {
		Depends $(ref) : $(source) ;
		LOCATE on $(ref) = [ on $(source) return $(LOCATE) ] ;
	}
}

rule ObjectReferences
{
	# ObjectReferences <source objects>
	# Creates local references to <source objects>, i.e. identifiers with the
	# current grist referring to the same files. <source objects> must have
	# already been LOCATEd.

	local source ;
	for source in $(1) {
		ObjectReference [ FGristFiles $(source) ] : $(source) ;
	}
}


rule CopySetHaikuRevision target : source
{
	# CopySetHaikuRevision <target> : <source>
	#
	# Copy <source> to <target>, writing the Git revision of the working
	# directory into the haiku revision section of <target>.
	#
	# <target> - Output file target. Gristed and located target.
	# <source> - ELF object to be copied. Gristed and located target.

	PropagateContainerUpdateTargetFlags $(target) : $(source) ;

	HAIKU_TARGET_IS_EXECUTABLE on $(target) = [ on $(source)
		return $(HAIKU_TARGET_IS_EXECUTABLE) ] ;

	local revisionFile = [ DetermineHaikuRevision ] ;

	Depends $(target)
		: <build>copyattr <build>set_haiku_revision $(source) $(revisionFile) ;
	CopySetHaikuRevision1 $(target)
		: <build>copyattr <build>set_haiku_revision $(source) $(revisionFile) ;
}


actions CopySetHaikuRevision1
{
	$(HOST_ADD_BUILD_COMPATIBILITY_LIB_DIR)

	$(2[1]) --data $(2[3]) $(1) || exit 1

	revision=0
	if [ -n "$(2[4]:E=)" ]; then
		revision="`cat $(2[4]:E=)`"
	fi
	$(2[2]) $(1) "$revision"
}


rule DetermineHaikuRevision
{
	# If existing, make the target depend on the .git/index file in the
	# root directory, so it gets updated when the revision changes due to
	# commits or merges.
	local gitIndex = <haiku-rootdir-git>index ;
	local revisionFile = <haiku-rootdir>haiku-revision ;
	if ! [ on $(gitIndex) return $(HAIKU_GIT_REVISION_DETERMINED) ] {
		HAIKU_GIT_REVISION_DETERMINED on $(gitIndex) = 1 ;
		MakeLocate $(revisionFile) : $(HAIKU_BUILD_OUTPUT_DIR) ;
		LocalClean clean : $(revisionFile) ;
		if $(HAIKU_REVISION) {
			DetermineHaikuRevision2 $(revisionFile) ;
		} else if [ Glob [ FDirName $(HAIKU_TOP) .git ] : index ] {
			SEARCH on $(gitIndex) = [ FDirName $(HAIKU_TOP) .git ] ;
			Depends $(revisionFile) : $(gitIndex) ;
			DetermineHaikuRevision1 $(revisionFile) : $(gitIndex) ;
		} else {
			revisionFile = ;
		}
	}

	return $(revisionFile) ;
}


actions DetermineHaikuRevision1
{
	$(HAIKU_TOP)/build/scripts/determine_haiku_revision $(HAIKU_TOP) $(1)
}


actions DetermineHaikuRevision2
{
	echo $(HAIKU_REVISION) > $(1)
}


rule DetermineEffectiveHaikuRevision
{
	local revisionFile = <haiku-rootdir>effective-haiku-revision ;
	if ! [ on $(revisionFile) return $(HAIKU_EFFECTIVE_REVISION_DETERMINED) ] {
		HAIKU_EFFECTIVE_REVISION_DETERMINED on $(revisionFile) = 1 ;
		MakeLocate $(revisionFile) : $(HAIKU_BUILD_OUTPUT_DIR) ;
		local rawRevision = [ DetermineHaikuRevision ] ;
		Depends $(revisionFile) : $(rawRevision) ;
		DetermineEffectiveHaikuRevision1 $(revisionFile) : $(rawRevision) ;
		LocalClean clean : $(revisionFile) ;
	}

	return $(revisionFile) ;
}


actions DetermineEffectiveHaikuRevision1
{
	revision=`sed -n 's,^\(hrev[0-9]*\).*,\1,p' "$(2:E=unknown-revision)"`
	if [ -z "$revision" ]; then
		echo "Error: unable to determine the effective Haiku revision."
		echo "       If you are using a Haiku clone without tags, you can set"
		echo "       the revision tag to use with e.g. HAIKU_REVISION=hrev43210"
		exit 1
	fi
	echo "${revision:-0}" > "$(1)"
}


rule DataFileToSourceFile sourceFile : dataFile : dataVariable : sizeVariable
{
	sourceFile = [ FGristFiles $(sourceFile) ] ;
	MakeLocateCommonPlatform $(sourceFile) ;

	sizeVariable ?= $(dataVariable)Size ;

	DATA_VARIABLE on $(sourceFile) = $(dataVariable) ;
	SIZE_VARIABLE on $(sourceFile) = $(sizeVariable) ;

	Depends $(sourceFile) : <build>data_to_source $(dataFile) ;
	DataFileToSourceFile1 $(sourceFile) : <build>data_to_source $(dataFile) ;
	LocalClean clean : $(sourceFile) ;
}

actions DataFileToSourceFile1
{
	$(HOST_ADD_BUILD_COMPATIBILITY_LIB_DIR)
	$(2[1]) $(DATA_VARIABLE) $(SIZE_VARIABLE) $(2[2]) $(1)
}

rule DownloadLocatedFile target : url : source
{
	# DownloadLocatedFile <target> : <url> [ : <source> ] ;
	#
	# <source> is an optional target that <target> will be made dependent on.
	# Its resolved path can be used in <url> via '$source'.

	URL on $(target) = $(url) ;

	if $(source) {
		Depends $(target) : $(source) ;
	}

	DownloadLocatedFile1 $(target) : $(source) ;
}

actions DownloadLocatedFile1
{
	source="$(2)"
	if [ "$(HAIKU_NO_DOWNLOADS)" = 1 ]; then
		echo "ERROR: Would need to download $(URL), but HAIKU_NO_DOWNLOADS is set!"
		exit 1
	fi
	wget --retry-connrefused --timeout 10 -O "$(1)" $(URL) || exit 1
	touch "$(1)"
}

rule DownloadFile file : url : source
{
	# DownloadFile <file> : <url> [ : <source> ] ;
	#
	# <source> is an optional target that the target will be made dependent on.
	# Its resolved path can be used in <url> via '$source'.

	file = $(file:G=download) ;

	# Request the download only once.
	if [ on $(file) return $(HAIKU_FILE_DOWNLOAD) ] {
		return $(file) ;
	}

	HAIKU_FILE_DOWNLOAD on $(file) = 1 ;

	MakeLocate $(file) : $(HAIKU_DOWNLOAD_DIR) ;
	DownloadLocatedFile $(file) : $(url) : $(source) ;

	return $(file) ;
}


actions ChecksumFileSHA256
{
	$(HOST_SHA256) $(2) \
		| $(HOST_EXTENDED_REGEX_SED) 's,([^[:space:]]*).*,\1,' > $(1)
		# The sed part is only necessary for sha256sum, but it doesn't harm for
		# sha256 either.
}


rule Sed target : source : substitutions : targetMap
{
	# Sed <target> : [ <source> ] : <substitutions> [ : <targetMap> ] ;
	#
	# Performs substitutions in a text file. If <source> is given, that is the
	# input, otherwise the substitutions are performed in place on <target>. The
	# caller is responsible for locating <target>, <source>, and any other used
	# target.
	#
	# <target> - The target file.
	# <source> - The source file. If not given, the substitutions are performed
	#	in place on <target>. If given, a dependency of <target> to <source>
	#	will be established.
	# <substitutions> - List of substitutions to be performed. Each element
	#	specifies a substitution. It's a partial sed "s" command of the form
	#	"<pattern>,<replacement>".
	# <targetMap> - A list of elements of the form "<variable>=<mappedTarget>".
	#	<variable> specifies a name of a shell variable, <mappedTarget> a jam
	#	target whose bound name will be assigned to the shell variable. The
	#	variable can be used in <substitutions>. A dependency of <target> to
	#	<mappedTarget> will be established.

	# We need a temporary (shell) file to which we write the target variable
	# mappings and the sed invocations. This is necessary, since multiple rule
	# invocations are allowed for a target, so that we cannot use on-target
	# variables.
	local script = [ NextID ] ;
	script = temp-sed-script-$(target:BS)-$(script) ;

	# process the target variable mappings
	local mappedTargets ;
	local targetMapElement ;
	for targetMapElement in $(targetMap) {
		local split = [ Match ([^=]+)=(.*) : $(targetMapElement) ] ;
		HAIKU_SED_SCRIPT_VARIABLE on $(script) += $(split[1]) ;
		local mappedTarget = $(split[2]) ;
		mappedTargets += $(mappedTarget) ;
	}

	HAIKU_SED_SCRIPT_SUBSTITUTIONS on $(script)
		= "-e \"s,$(substitutions),g\"" ;
	HAIKU_SED_SCRIPT_SOURCE on $(script) = $(source) ;
	if $(source) {
		HAIKU_SED_SCRIPT_SOURCE_ARGUMENTS on $(script) = ">" ;
	} else {
		HAIKU_SED_SCRIPT_SOURCE_ARGUMENTS on $(script) = -i ;
	}

	# build the script
	MakeLocate $(script) : $(HAIKU_TMP_DIR) ;
	Depends $(script) : $(mappedTargets) $(source) ;
	SedCreateScript $(script) : $(mappedTargets) ;

	# build the target
	Depends $(target) : $(script) ;
	Sed1 $(target) : $(script) ;
	RmTemps $(target) : $(script) ;
}


actions SedCreateScript bind HAIKU_SED_SCRIPT_SOURCE
{
	set -o errexit

	$(RM) "$(1)"
	touch "$(1)"

	set -- $(2)
	for variable in "$(HAIKU_SED_SCRIPT_VARIABLE)" ; do
		echo "$variable=\"$1\"" >> "$(1)"
		shift
	done

	echo sed '$(HAIKU_SED_SCRIPT_SUBSTITUTIONS)' \
		'"$(HAIKU_SED_SCRIPT_SOURCE)"' "$(HAIKU_SED_SCRIPT_SOURCE_ARGUMENTS)" \
		'"$target"' >> "$(1)"
}


actions Sed1
{
	set -o errexit

	target="$(1)"
	. "$(2)"
}


rule StripFile target : source
{
	# Note: The caller is reponsible for matching TARGET_PACKAGING_ARCH with
	# the architecture the target was built for.
	STRIP on $(target) = $(HAIKU_STRIP_$(TARGET_PACKAGING_ARCH)) ;

	PropagateContainerUpdateTargetFlags $(target) : $(source) ;

	LocalClean clean : $(target) ;
	Depends $(target) : $(source) <build>xres <build>copyattr ;
	StripFile1 $(target) : $(source) <build>xres <build>copyattr ;
}


actions StripFile1
{
	$(HOST_ADD_BUILD_COMPATIBILITY_LIB_DIR)
	"$(STRIP)" -o "$(1)" "$(2[1])"
	"$(2[2])" -o "$(1)" "$(2[1])"
	"$(2[3])" "$(2[1])" "$(1)"
}


rule StripFiles files
{
	# Note: The caller is reponsible for matching TARGET_PACKAGING_ARCH with
	# the architecture the targets were built for.
	local strippedFiles ;
	local file ;
	for file in $(files) {
		local strippedFile = $(file:G=stripped_$(file:G)) ;
		# Place the stripped file in a "stripped" subdirectory of the file's
		# location.
		local location = [ on $(file) return $(LOCATE) ] ;
		if ! $(location) {
			location
				= $(TARGET_COMMON_DEBUG_OBJECT_DIR_$(TARGET_PACKAGING_ARCH)) ;
		}
		MakeLocateArch $(strippedFile) : [ FDirName $(location) stripped ] ;
		StripFile $(strippedFile) : $(file) ;
		strippedFiles += $(strippedFile) ;
	}

	return $(strippedFiles) ;
}