/* $Id$ */
/* Copyright (c) 2006-2022 Pierre Pronchery <khorben@defora.org> */
/* This file is part of DeforaOS Devel configure */
/* All rights reserved.
 *
 * Redistribution and use in source and binary forms, with or without
 * modification, are permitted provided that the following conditions are
 * met:
 *
 * 1. Redistributions of source code must retain the above copyright
 *    notice, this list of conditions and the following disclaimer.
 *
 * 2. Redistributions in binary form must reproduce the above copyright
 *    notice, this list of conditions and the following disclaimer in the
 *    documentation and/or other materials provided with the distribution.
 *
 * THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS
 * IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED
 * TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A
 * PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT
 * HOLDER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL,
 * SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED
 * TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR
 * PROFITS; OR BUSINESS INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF
 * LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING
 * NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE OF THIS
 * SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE. */



#include <System.h>
#include <sys/stat.h>
#include <unistd.h>
#include <stdarg.h>
#include <stdlib.h>
#include <stdio.h>
#include <string.h>
#include <libgen.h>
#include <errno.h>
#include "common.h"
#include "settings.h"
#include "configure.h"
#include "../config.h"

#ifndef PROGNAME_CONFIGURE
# define PROGNAME_CONFIGURE PACKAGE
#endif


/* private */
/* types */
typedef struct _Makefile
{
	Configure * configure;
	FILE * fp;
} Makefile;


/* prototypes */
/* accessors */
static String const * _makefile_get_config(Makefile * makefile,
		String const * section, String const * variable);
static String const * _makefile_get_config_mode(Makefile * makefile,
		String const * mode, String const * variable);
static TargetType _makefile_get_type(Makefile * makefile,
		String const * target);

static int _makefile_is_enabled(Makefile * makefile, char const * target);
static unsigned int _makefile_is_flag_set(Makefile * makefile,
		unsigned int flag);
static int _makefile_is_phony(Makefile * makefile, char const * target);

/* useful */
static int _makefile_expand(Makefile * makefile, char const * field);
static int _makefile_link(Makefile * makefile, int symlink, char const * link,
		char const * path);
static int _makefile_output_extension(Makefile * makefile,
		String const * extension);
static int _makefile_output_path(Makefile * makefile, String const * path);
static int _makefile_output_program(Makefile * makefile, String const * name,
		unsigned int override);
static int _makefile_output_variable(Makefile * makefile, String const * name,
		String const * value);
static int _makefile_mkdir(Makefile * makefile, char const * directory);
static int _makefile_print(Makefile * makefile, char const * format, ...);
static int _makefile_print_escape(Makefile * makefile, char const * str);
static int _makefile_print_escape_variable(Makefile * makefile,
		char const * str);
static int _makefile_print_target(Makefile * makefile, String const * target);
static int _makefile_remove(Makefile * makefile, int recursive, ...);
static int _makefile_subdirs(Makefile * makefile, char const * target);
static int _makefile_target(Makefile * makefile, char const * target, ...);
#ifdef WITH_UNUSED
static int _makefile_targetv(Makefile * makefile, char const * target,
		char const ** depends);
#endif


/* functions */
/* makefile */
static int _makefile_write(Makefile * makefile, configArray * ca,
	       	int from, int to);

int makefile(Configure * configure, String const * directory, configArray * ca,
	       	int from, int to)
		
{
	Makefile makefile;
	String * filename;
	int ret = 0;

	if(directory == NULL || string_get_length(directory) == 0)
		filename = string_new(MAKEFILE);
	else
		filename = string_new_append(directory, "/", MAKEFILE, NULL);
	if(filename == NULL)
		return -1;
	makefile.configure = configure;
	makefile.fp = NULL;
	if(!_makefile_is_flag_set(&makefile, PREFS_n)
			&& (makefile.fp = fopen(filename, "w")) == NULL)
		ret = configure_error(1, "%s: %s", filename, strerror(errno));
	else
	{
		if(_makefile_is_flag_set(&makefile, PREFS_v))
			printf("%s%s\n", "Creating ", filename);
		ret |= _makefile_write(&makefile, ca, from, to);
		if(makefile.fp != NULL)
			fclose(makefile.fp);
	}
	string_delete(filename);
	return ret;
}

static int _write_variables(Makefile * makefile);
static int _write_targets(Makefile * makefile);
static int _write_objects(Makefile * makefile);
static int _write_clean(Makefile * makefile);
static int _write_distclean(Makefile * makefile);
static int _write_dist(Makefile * makefile, configArray * ca, int from, int to);
static int _write_distcheck(Makefile * makefile);
static int _write_install(Makefile * makefile);
static int _write_phony(Makefile * makefile,
		char const ** targets);
static int _write_uninstall(Makefile * makefile);
static int _makefile_write(Makefile * makefile, configArray * ca,
	       	int from, int to)
{
	char const * depends[9] = { "all" };
	size_t i = 1;

	if(_write_variables(makefile) != 0
			|| _write_targets(makefile) != 0
			|| _write_objects(makefile) != 0
			|| _write_clean(makefile) != 0
			|| _write_distclean(makefile) != 0
			|| _write_dist(makefile, ca, from, to) != 0
			|| _write_distcheck(makefile) != 0
			|| _write_install(makefile) != 0
			|| _write_uninstall(makefile) != 0)
		return 1;
	if(_makefile_get_config_mode(makefile, NULL, "subdirs") != NULL)
		depends[i++] = "subdirs";
	depends[i++] = "clean";
	depends[i++] = "distclean";
	if(_makefile_get_config_mode(makefile, NULL, "package") != NULL
			&& _makefile_get_config_mode(makefile, NULL,
				"version") != NULL)
	{
		depends[i++] = "dist";
		depends[i++] = "distcheck";
	}
	depends[i++] = "install";
	depends[i++] = "uninstall";
	depends[i++] = NULL;
	return _write_phony(makefile, depends);
}

static int _variables_package(Makefile * makefile,
		String const * directory);
static int _variables_print(Makefile * makefile,
	       	char const * input, char const * output);
static int _variables_dist(Makefile * makefile, char * done);
static int _variables_targets(Makefile * makefile);
static int _variables_executables(Makefile * makefile, char * done);
static int _variables_includes(Makefile * makefile);
static int _variables_subdirs(Makefile * makefile);
static int _write_variables(Makefile * makefile)
{
	int ret = 0;
	String const * directory;
	char done[TT_LAST]; /* FIXME even better if'd be variable by variable */

	memset(&done, 0, sizeof(done));
	directory = _makefile_get_config(makefile, NULL, "_directory");
	ret |= _variables_package(makefile, directory);
	ret |= _variables_print(makefile, "subdirs", "SUBDIRS");
	ret |= _variables_dist(makefile, done);
	ret |= _variables_targets(makefile);
	ret |= _variables_executables(makefile, done);
	ret |= _variables_includes(makefile);
	ret |= _variables_subdirs(makefile);
	_makefile_print(makefile, "\n");
	return ret;
}

static int _variables_package(Makefile * makefile,
		String const * directory)
{
	String const * package;
	String const * version;
	String const * vendor;
	String const * p;

	if((package = _makefile_get_config_mode(makefile, NULL, "package"))
			== NULL)
		return 0;
	if(_makefile_is_flag_set(makefile, PREFS_v))
		printf("%s%s", "Package: ", package);
	if((version = _makefile_get_config_mode(makefile, NULL, "version"))
			== NULL)
	{
		if(_makefile_is_flag_set(makefile, PREFS_v))
			fputc('\n', stdout);
		fprintf(stderr, "%s%s%s", PROGNAME_CONFIGURE ": ", directory,
				": \"package\" needs \"version\"\n");
		return 1;
	}
	if(_makefile_is_flag_set(makefile, PREFS_v))
		printf(" %s\n", version);
	if((vendor = _makefile_get_config_mode(makefile, NULL, "vendor"))
			== NULL)
		vendor = "DeforaOS";
	_makefile_output_variable(makefile, "PACKAGE", package);
	_makefile_output_variable(makefile, "VERSION", version);
	_makefile_output_variable(makefile, "VENDOR", vendor);
	if((p = _makefile_get_config_mode(makefile, NULL, "config")) != NULL)
		return settings(makefile->configure, directory, package,
				version, vendor);
	return 0;
}

static int _variables_print(Makefile * makefile,
		char const * input, char const * output)
{
	String const * p;
	String * prints;
	String * q;
	unsigned long i;
	char c;

	if((p = _makefile_get_config_mode(makefile, NULL, input)) == NULL)
		return 0;
	if((prints = string_new(p)) == NULL)
		return 1;
	q = prints;
	_makefile_print(makefile, "%s%s", output, "\t=");
	for(i = 0;; i++)
	{
		if(prints[i] != ',' && prints[i] != '\0')
			continue;
		c = prints[i];
		prints[i] = '\0';
		if(strchr(prints, ' ') != NULL)
			_makefile_print(makefile, " \"%s\"", prints);
		else
			_makefile_print(makefile, " %s", prints);
		if(c == '\0')
			break;
		prints += i + 1;
		i = 0;
	}
	_makefile_print(makefile, "\n");
	string_delete(q);
	return 0;
}

static int _variables_dist(Makefile * makefile, char * done)
{
	String const * p;
	String * dist;
	String * q;
	size_t i;
	char c;

	if((p = _makefile_get_config_mode(makefile, NULL, "dist")) == NULL)
		return 0;
	if((dist = string_new(p)) == NULL)
		return 1;
	q = dist;
	for(i = 0;; i++)
	{
		if(dist[i] != ',' && dist[i] != '\0')
			continue;
		c = dist[i];
		dist[i] = '\0';
		if(_makefile_get_config(makefile, dist, "install") != NULL)
		{
			_makefile_output_path(makefile, "objdir");
			_makefile_output_path(makefile, "prefix");
			_makefile_output_path(makefile, "destdir");
			done[TT_SCRIPT] = 1;
			_makefile_output_program(makefile, "mkdir", 0);
			_makefile_output_program(makefile, "install", 0);
			_makefile_output_program(makefile, "rm", 0);
			break;
		}
		if(c == '\0')
			break;
		dist += i + 1;
		i = 0;
	}
	string_delete(q);
	return 0;
}

static int _variables_targets(Makefile * makefile)
{
	int ret = 0;
	String const * p;
	String * prints;
	String * q;
	size_t i;
	char c;

	if((p = _makefile_get_config_mode(makefile, NULL, "targets")) == NULL)
		return 0;
	if((prints = string_new(p)) == NULL)
		return 1;
	q = prints;
	_makefile_print(makefile, "%s", "TARGETS\t=");
	for(i = 0;; i++)
	{
		if(prints[i] != ',' && prints[i] != '\0')
			continue;
		c = prints[i];
		prints[i] = '\0';
		_makefile_print(makefile, " ");
		_makefile_print_target(makefile, prints);
		if(c == '\0')
			break;
		prints += i + 1;
		i = 0;
	}
	_makefile_print(makefile, "\n");
	string_delete(q);
	return ret;
}

static void _executables_variables(Makefile * makefile,
	       	String const * target, char * done);
static int _variables_executables(Makefile * makefile, char * done)
{
	String const * targets;
	String const * includes;
	String const * package;
	String * p;
	String * q;
	size_t i;
	char c;

	targets = _makefile_get_config_mode(makefile, NULL, "targets");
	includes = _makefile_get_config_mode(makefile, NULL, "includes");
	package = _makefile_get_config_mode(makefile, NULL, "package");
	if(targets != NULL)
	{
		if((p = string_new(targets)) == NULL)
			return 1;
		q = p;
		for(i = 0;; i++)
		{
			if(p[i] != ',' && p[i] != '\0')
				continue;
			c = p[i];
			p[i] = '\0';
			_executables_variables(makefile, p, done);
			if(c == '\0')
				break;
			p += i + 1;
			i = 0;
		}
		string_delete(q);
	}
	else if(includes != NULL)
	{
		_makefile_output_path(makefile, "objdir");
		_makefile_output_path(makefile, "prefix");
		_makefile_output_path(makefile, "destdir");
	}
	if(targets != NULL || includes != NULL || package != NULL)
	{
		_makefile_output_program(makefile, "rm", 0);
		_makefile_output_program(makefile, "ln", 0);
	}
	if(package != NULL)
	{
		_makefile_output_program(makefile, "tar", 0);
		_makefile_output_extension(makefile, "tgz");
		_makefile_output_program(makefile, "mkdir", 0);
	}
	if(targets != NULL || includes != NULL)
	{
		if(package == NULL)
			_makefile_output_program(makefile, "mkdir", 0);
		_makefile_output_program(makefile, "install", 0);
	}
	return 0;
}

static void _variables_binary(Makefile * makefile, char * done);
static void _variables_library(Makefile * makefile, char * done);
static void _variables_library_static(Makefile * makefile);
static void _variables_libtool(Makefile * makefile, char * done);
static void _variables_script(Makefile * makefile, char * done);
static void _executables_variables(Makefile * makefile, String const * target,
		char * done)
{
	String const * type;
	TargetType tt;

	if((type = _makefile_get_config(makefile, target, "type")) == NULL)
		return;
	if(done[(tt = enum_string(TT_LAST, sTargetType, type))])
		return;
	switch(tt)
	{
		case TT_BINARY:
			_variables_binary(makefile, done);
			done[TT_OBJECT] = 1;
			break;
		case TT_COMMAND:
			break;
		case TT_OBJECT:
			_variables_binary(makefile, done);
			done[TT_BINARY] = 1;
			break;
		case TT_LIBRARY:
			_variables_library(makefile, done);
			done[TT_PLUGIN] = 1;
			break;
		case TT_PLUGIN:
			_variables_library(makefile, done);
			done[TT_LIBRARY] = 1;
			break;
		case TT_LIBTOOL:
			_variables_libtool(makefile, done);
			break;
		case TT_SCRIPT:
			_variables_script(makefile, done);
			done[TT_SCRIPT] = 1;
			break;
		case TT_UNKNOWN:
			break;
	}
	done[tt] = 1;
	return;
}

static void _targets_asflags(Makefile * makefile);
static void _targets_cflags(Makefile * makefile);
static void _targets_cxxflags(Makefile * makefile);
static void _targets_exeext(Makefile * makefile);
static void _targets_goflags(Makefile * makefile);
static void _targets_ldflags(Makefile * makefile);
static void _targets_jflags(Makefile * makefile);
static void _targets_vflags(Makefile * makefile);
static void _binary_ldflags(Makefile * makefile, String const * ldflags);
static void _variables_binary(Makefile * makefile, char * done)
{
	if(!done[TT_LIBRARY] && !done[TT_SCRIPT])
	{
		_makefile_output_path(makefile, "objdir");
		_makefile_output_path(makefile, "prefix");
		_makefile_output_path(makefile, "destdir");
	}
	_makefile_output_path(makefile, "bindir");
	_makefile_output_path(makefile, "sbindir");
	if(!done[TT_LIBRARY])
	{
		_targets_asflags(makefile);
		_targets_cflags(makefile);
		_targets_cxxflags(makefile);
		_targets_ldflags(makefile);
		_targets_goflags(makefile);
		_targets_jflags(makefile);
		_targets_vflags(makefile);
		_targets_exeext(makefile);
	}
}

static void _targets_asflags(Makefile * makefile)
{
	String const * as;
	String const * asf;
	String const * asff;

	as = _makefile_get_config_mode(makefile, NULL, "as");
	asff = _makefile_get_config_mode(makefile, NULL, "asflags_force");
	asf = _makefile_get_config_mode(makefile, NULL, "asflags");
	if(as != NULL || asff != NULL || asf != NULL)
	{
		_makefile_output_variable(makefile, "AS", (as != NULL) ? as
				: configure_get_program(makefile->configure,
					"as"));
		_makefile_output_variable(makefile, "ASFLAGSF", asff);
		_makefile_output_variable(makefile, "ASFLAGS", asf);
	}
}

static void _targets_cflags(Makefile * makefile)
{
	String const * cc;
	String const * cff;
	String const * cf;
	String const * cppf;
	String const * cpp;
	String * p;
	HostOS os;

	/* TODO: only output when the targets are relevant */
	cppf = _makefile_get_config_mode(makefile, NULL, "cppflags_force");
	cpp = _makefile_get_config_mode(makefile, NULL, "cppflags");
	cff = _makefile_get_config_mode(makefile, NULL, "cflags_force");
	cf = _makefile_get_config_mode(makefile, NULL, "cflags");
	cc = _makefile_get_config_mode(makefile, NULL, "cc");
	if(cppf == NULL && cpp == NULL && cff == NULL && cf == NULL
			&& cc == NULL)
		return;
	_makefile_output_program(makefile, "cc", 1);
	_makefile_output_variable(makefile, "CPPFLAGSF", cppf);
	_makefile_output_variable(makefile, "CPPFLAGS", cpp);
	p = NULL;
	if((os = configure_get_os(makefile->configure)) == HO_GNU_LINUX
			&& cff != NULL && string_find(cff, "-ansi"))
		p = string_new_append(cff, " -D _GNU_SOURCE");
	_makefile_output_variable(makefile, "CFLAGSF", (p != NULL) ? p : cff);
	string_delete(p);
	p = NULL;
	if(os == HO_GNU_LINUX && cf != NULL && string_find(cf, "-ansi"))
		p = string_new_append(cf, " -D _GNU_SOURCE");
	_makefile_output_variable(makefile, "CFLAGS", (p != NULL) ? p : cf);
	string_delete(p);
}

static void _targets_cxxflags(Makefile * makefile)
{
	String const * cxx;
	String const * cxxff;
	String const * cxxf;

	cxx = _makefile_get_config_mode(makefile, NULL, "cxx");
	cxxff = _makefile_get_config_mode(makefile, NULL, "cxxflags_force");
	cxxf = _makefile_get_config_mode(makefile, NULL, "cxxflags");
	if(cxx != NULL || cxxff != NULL || cxxf != NULL)
		_makefile_output_program(makefile, "cxx", 1);
	if(cxxff != NULL)
	{
		_makefile_print(makefile, "%s%s", "CXXFLAGSF= ", cxxff);
		if(configure_get_os(makefile->configure) == HO_GNU_LINUX
				&& string_find(cxxff, "-ansi"))
			_makefile_print(makefile, "%s", " -D _GNU_SOURCE");
		_makefile_print(makefile, "\n");
	}
	if(cxxf != NULL)
	{
		_makefile_print(makefile, "%s%s", "CXXFLAGS= ", cxxf);
		if(configure_get_os(makefile->configure) == HO_GNU_LINUX
				&& string_find(cxxf, "-ansi"))
			_makefile_print(makefile, "%s", " -D _GNU_SOURCE");
		_makefile_print(makefile, "\n");
	}
}

static void _targets_exeext(Makefile * makefile)
{
	_makefile_output_extension(makefile, "exe");
}

static void _targets_goflags(Makefile * makefile)
{
	String const * p;
	String * targets;
	String * q;
	size_t i;
	char c;
	String const * go = NULL;
	String const * gof;
	String const * goff;

	/* TODO: simplify the detection for Go targets */
	if((p = _makefile_get_config_mode(makefile, NULL, "targets")) == NULL)
		return;
	if((targets = string_new(p)) == NULL)
		/* FIXME report the error */
		return;
	q = targets;
	for(i = 0;; i++)
	{
		if(targets[i] != ',' && targets[i] != '\0')
			continue;
		c = targets[i];
		targets[i] = '\0';
		if((p = _makefile_get_config(makefile, targets, "sources"))
				!= NULL
				/* XXX only checks the last source */
				&& (p = source_extension(p)) != NULL
				&& source_type(p) == OT_GOLANG_SOURCE)
		{
			if((go = _makefile_get_config_mode(makefile, NULL,
							"go")) == NULL)
				go = "go";
		}
		if(c == '\0')
			break;
		targets += i + 1;
		i = 0;
	}
	string_delete(q);
	goff = _makefile_get_config_mode(makefile, NULL, "goflags_force");
	gof = _makefile_get_config_mode(makefile, NULL, "goflags");
	if(go != NULL || goff != NULL || gof != NULL)
	{
		_makefile_output_program(makefile, "go", 1);
		_makefile_output_variable(makefile, "GOFLAGSF", goff);
		_makefile_output_variable(makefile, "GOFLAGS", gof);
	}
}

static void _targets_ldflags(Makefile * makefile)
{
	String const * p;

	if((p = _makefile_get_config_mode(makefile, NULL, "ldflags_force"))
			!= NULL)
	{
		_makefile_print(makefile, "%s", "LDFLAGSF=");
		_binary_ldflags(makefile, p);
		_makefile_print(makefile, "\n");
	}
	if((p = _makefile_get_config_mode(makefile, NULL, "ldflags")) != NULL)
	{
		_makefile_print(makefile, "%s", "LDFLAGS\t=");
		_binary_ldflags(makefile, p);
		_makefile_print(makefile, "\n");
	}
}

static void _targets_jflags(Makefile * makefile)
{
	String const * j;
	String const * jff;
	String const * jf;

	j = _makefile_get_config_mode(makefile, NULL, "javac");
	jff = _makefile_get_config_mode(makefile, NULL, "jflags_force");
	jf = _makefile_get_config_mode(makefile, NULL, "jflags");
	if(j != NULL || jff != NULL || jf != NULL)
		_makefile_output_program(makefile, "javac", 1);
	if(jff != NULL)
		_makefile_output_variable(makefile, "JFLAGSF", jff);
	if(jf != NULL)
		_makefile_output_variable(makefile, "JFLAGS", jf);
}

static void _targets_vflags(Makefile * makefile)
{
	String const * v;
	String const * vff;
	String const * vf;

	v = _makefile_get_config_mode(makefile, NULL, "verilog");
	vff = _makefile_get_config_mode(makefile, NULL, "vflags_force");
	vf = _makefile_get_config_mode(makefile, NULL, "vflags");
	if(v != NULL || vff != NULL || vf != NULL)
		_makefile_output_program(makefile, "verilog", 1);
	if(vff != NULL)
		_makefile_output_variable(makefile, "VFLAGSF", vff);
	if(vf != NULL)
		_makefile_output_variable(makefile, "VFLAGS", vf);
}

static void _binary_ldflags(Makefile * makefile, String const * ldflags)
{
	char const * libs_bsd[] = { "dl", "resolv", "ossaudio", "socket",
		"ws2_32", NULL };
	char const * libs_darwin[] = { "crypt", "ossaudio", "socket", "ws2_32",
		NULL };
	char const * libs_deforaos[] = { "ossaudio", "resolv", "ssl", "ws2_32",
		NULL };
	char const * libs_gnu[] = { "intl", "ossaudio", "resolv", "socket",
		"ws2_32", NULL };
	char const * libs_netbsd[] = { "dl", "resolv", "socket", "ws2_32",
		NULL };
	char const * libs_sunos[] = { "dl", "ossaudio", "ws2_32", NULL };
	char const * libs_win32[] = { "dl", "ossaudio", NULL };
	char const * flags_darwin[] = { "-Wl,--export-dynamic",
		"--export-dynamic", NULL };
	char buf[16];
	char const ** libs;
	char const ** flags = NULL;
	String * p;
	String * q;
	size_t i;

	if((p = string_new(ldflags)) == NULL) /* XXX report error? */
	{
		_makefile_print(makefile, " %s%s", ldflags, "\n");
		return;
	}
	switch(configure_get_os(makefile->configure))
	{
		case HO_DARWIN:
			libs = libs_darwin;
			flags = flags_darwin;
			break;
		case HO_DEFORAOS:
			libs = libs_deforaos;
			break;
		case HO_FREEBSD:
		case HO_OPENBSD:
			libs = libs_bsd;
			break;
		case HO_GNU_LINUX:
			libs = libs_gnu;
			break;
		case HO_NETBSD:
			libs = libs_netbsd;
			break;
		case HO_SUNOS:
			libs = libs_sunos;
			break;
		case HO_WIN32:
			libs = libs_win32;
			break;
		default:
			libs = libs_gnu;
			break;
	}
	for(i = 0; libs[i] != NULL; i++)
	{
		snprintf(buf, sizeof(buf), "-l%s", libs[i]);
		if((q = string_find(p, buf)) == NULL)
			continue;
		memmove(q, q + strlen(buf), strlen(q) - strlen(buf) + 1);
	}
	for(i = 0; flags != NULL && flags[i] != NULL; i++)
	{
		if((q = string_find(p, flags[i])) == NULL)
			continue;
		memmove(q, q + string_get_length(flags[i]),
				string_get_length(q
					+ string_get_length(flags[i])) + 1);
	}
	_makefile_print(makefile, " %s", p);
	string_delete(p);
}

static void _variables_library(Makefile * makefile, char * done)
{
	String const * p;

	if(!done[TT_LIBRARY] && !done[TT_SCRIPT])
	{
		_makefile_output_path(makefile, "objdir");
		_makefile_output_path(makefile, "prefix");
		_makefile_output_path(makefile, "destdir");
	}
	_makefile_output_path(makefile, "libdir");
	if(!done[TT_BINARY])
	{
		_targets_asflags(makefile);
		_targets_cflags(makefile);
		_targets_cxxflags(makefile);
		_targets_ldflags(makefile);
		_targets_goflags(makefile);
		_targets_vflags(makefile);
		_targets_exeext(makefile);
	}
	if(configure_can_library_static(makefile->configure))
		_variables_library_static(makefile);
	if((p = _makefile_get_config_mode(makefile, NULL, "ld")) == NULL)
		_makefile_output_program(makefile, "ccshared", 0);
	else
		_makefile_output_variable(makefile, "CCSHARED", p);
	_makefile_output_extension(makefile, "so");
}

static void _variables_library_static(Makefile * makefile)
{
	String const * p;

	if((p = _makefile_get_config_mode(makefile, NULL, "ar")) == NULL)
		_makefile_output_program(makefile, "ar", 0);
	else
		_makefile_output_variable(makefile, "AR", p);
	_makefile_output_variable(makefile, "ARFLAGS", "-rc");
	if((p = _makefile_get_config_mode(makefile, NULL, "ranlib")) == NULL)
		_makefile_output_program(makefile, "ranlib", 0);
	else
		_makefile_output_variable(makefile, "RANLIB", p);
}

static void _variables_libtool(Makefile * makefile, char * done)
{
	_variables_library(makefile, done);
	if(!done[TT_LIBTOOL])
		_makefile_output_program(makefile, "libtool", 1);
}

static void _variables_script(Makefile * makefile, char * done)
{
	if(!done[TT_BINARY] && !done[TT_LIBRARY] && !done[TT_SCRIPT])
	{
		_makefile_output_path(makefile, "objdir");
		_makefile_output_path(makefile, "prefix");
		_makefile_output_path(makefile, "destdir");
	}
}

static int _variables_includes(Makefile * makefile)
{
	String const * includes;

	if((includes = _makefile_get_config_mode(makefile, NULL, "includes"))
			== NULL)
		return 0;
	if(makefile->fp == NULL)
		return 0;
	_makefile_output_path(makefile, "includedir");
	return 0;
}

static int _variables_subdirs(Makefile * makefile)
{
	String const * sections[] = { "dist" };
	size_t i;
	String * p;
	String const * q;

	if(_makefile_get_config_mode(makefile, NULL, "subdirs") == NULL
			|| _makefile_get_config_mode(makefile, NULL,
				"package") != NULL
			|| _makefile_get_config_mode(makefile, NULL,
				"targets") != NULL
			|| _makefile_get_config_mode(makefile, NULL,
				"includes") != NULL)
		return 0;
	for(i = 0; i < sizeof(sections) / sizeof(*sections); i++)
	{
		if((q = _makefile_get_config_mode(makefile, NULL, sections[i]))
				== NULL)
			continue;
		if((p = strdup(q)) == NULL)
			return -1;
		for(q = strtok(p, ","); q != NULL; q = strtok(NULL, ","))
			if(_makefile_get_config(makefile, q, "install") != NULL)
				break;
		free(p);
		if(q != NULL)
			return 0;
	}
	return _makefile_output_program(makefile, "mkdir", 0);
}

static int _targets_all(Makefile * makefile);
static int _targets_subdirs(Makefile * makefile);
static int _targets_target(Makefile * makefile, String const * target);
static int _write_targets(Makefile * makefile)
{
	int ret = 0;
	String const * p;
	String * targets;
	String * q;
	size_t i;
	char c;

	if(_targets_all(makefile) != 0
			|| _targets_subdirs(makefile) != 0)
		return 1;
	if((p = _makefile_get_config_mode(makefile, NULL, "targets")) == NULL)
		return 0;
	if((targets = string_new(p)) == NULL)
		return 1;
	q = targets;
	for(i = 0;; i++)
	{
		if(targets[i] != ',' && targets[i] != '\0')
			continue;
		c = targets[i];
		targets[i] = '\0';
		ret |= _targets_target(makefile, targets);
		if(c == '\0')
			break;
		targets += i + 1;
		i = 0;
	}
	string_delete(q);
	return ret;
}

static int _targets_all(Makefile * makefile)
{
	char const * depends[] = { NULL, NULL };
	size_t j = 0;
	String const * subdirs;
	String const * p;
	String * targets;
	String * q;
	size_t i;
	char c;
	int enabled = 1;

	if(_makefile_get_config(makefile, "all", "type") != NULL)
		return _targets_target(makefile, "all");
	if((subdirs = _makefile_get_config_mode(makefile, NULL, "subdirs"))
			!= NULL)
		depends[j++] = "subdirs";
	if((p = _makefile_get_config_mode(makefile, NULL, "targets")) != NULL
			&& string_get_length(p) > 0)
		depends[j++] = "$(TARGETS)";
	if(p == NULL || string_get_length(p) == 0)
	{
		_makefile_target(makefile, "all", depends[0], depends[1], NULL);
		return 0;
	}
	if((targets = string_new(p)) == NULL)
		return 1;
	q = targets;
	for(i = 0;; i++)
	{
		if(targets[i] != ',' && targets[i] != '\0')
			continue;
		c = targets[i];
		targets[i] = '\0';
		if(_makefile_is_enabled(makefile, targets) == 0)
			enabled = 0;
		if(c == '\0')
			break;
		targets[i] = c;
		targets += i + 1;
		i = 0;
	}
	if(enabled > 0)
		_makefile_target(makefile, "all", depends[0], depends[1], NULL);
	else
	{
		_makefile_print(makefile, "\nall:%s",
				(subdirs != NULL) ? " subdirs" : "");
		targets = q;
		for(i = 0;; i++)
		{
			if(targets[i] != ',' && targets[i] != '\0')
				continue;
			c = targets[i];
			targets[i] = '\0';
			if(_makefile_is_enabled(makefile, targets))
			{
				_makefile_print(makefile, " ");
				_makefile_print_target(makefile, targets);
			}
			if(c == '\0')
				break;
			targets += i + 1;
			i = 0;
		}
		_makefile_print(makefile, "\n");
	}
	string_delete(q);
	return 0;
}

static int _targets_subdirs(Makefile * makefile)
{
	String const * subdirs;

	if((subdirs = _makefile_get_config_mode(makefile, NULL, "subdirs"))
			!= NULL)
	{
		_makefile_target(makefile, "subdirs", NULL);
		_makefile_subdirs(makefile, NULL);
	}
	return 0;
}

static int _target_objs(Makefile * makefile, String const * target);
static int _target_binary(Makefile * makefile, String const * target);
static int _target_command(Makefile * makefile, String const * target);
static int _target_library(Makefile * makefile, String const * target);
static int _target_library_static(Makefile * makefile, String const * target);
static int _target_libtool(Makefile * makefile, String const * target);
static int _target_object(Makefile * makefile, String const * target);
static int _target_plugin(Makefile * makefile, String const * target);
static int _target_script(Makefile * makefile, String const * target);
static int _targets_target(Makefile * makefile, String const * target)
{
	String const * type;
	TargetType tt;

	if((type = _makefile_get_config(makefile, target, "type")) == NULL)
	{
		fprintf(stderr, "%s%s%s", PROGNAME_CONFIGURE ": ", target,
				": no type defined for target\n");
		return 1;
	}
	tt = enum_string(TT_LAST, sTargetType, type);
	switch(tt)
	{
		case TT_BINARY:
			return _target_binary(makefile, target);
		case TT_COMMAND:
			return _target_command(makefile, target);
		case TT_LIBRARY:
			return _target_library(makefile, target);
		case TT_LIBTOOL:
			return _target_libtool(makefile, target);
		case TT_OBJECT:
			return _target_object(makefile, target);
		case TT_PLUGIN:
			return _target_plugin(makefile, target);
		case TT_SCRIPT:
			return _target_script(makefile, target);
		case TT_UNKNOWN:
			fprintf(stderr, "%s%s%s", PROGNAME_CONFIGURE ": ",
					target, ": unknown type for target\n");
			return 1;
	}
	return 0;
}

static int _objs_source(Makefile * makefile, String * source, TargetType tt);
static int _target_objs(Makefile * makefile, String const * target)
{
	int ret = 0;
	String const * p;
	TargetType tt = TT_UNKNOWN;
	String * sources;
	String * q;
	size_t i;
	char c;

	if((p = _makefile_get_config(makefile, target, "type")) != NULL)
		tt = enum_string(TT_LAST, sTargetType, p);
	if((p = _makefile_get_config(makefile, target, "sources")) == NULL)
		/* a binary target may have no sources */
		return 0;
	if((sources = string_new(p)) == NULL)
		return 1;
	q = sources;
	_makefile_print(makefile, "\n");
	_makefile_print_escape_variable(makefile, target);
	_makefile_print(makefile, "%s", "_OBJS =");
	for(i = 0; ret == 0; i++)
	{
		if(sources[i] != ',' && sources[i] != '\0')
			continue;
		c = sources[i];
		sources[i] = '\0';
		ret = _objs_source(makefile, sources, tt);
		if(c == '\0')
			break;
		sources += i + 1;
		i = 0;
	}
	_makefile_print(makefile, "\n");
	string_delete(q);
	return ret;
}

static int _objs_source(Makefile * makefile, String * source, TargetType tt)
{
	int ret = 0;
	String const * extension;
	size_t len;

	if((extension = source_extension(source)) == NULL)
	{
		fprintf(stderr, "%s%s%s", PROGNAME_CONFIGURE ": ", source,
				": no extension for source\n");
		return 1;
	}
	len = string_get_length(source) - string_get_length(extension) - 1;
	source[len] = '\0';
	switch(source_type(extension))
	{
		case OT_ASM_SOURCE:
		case OT_ASMPP_SOURCE:
		case OT_C_SOURCE:
		case OT_CXX_SOURCE:
		case OT_OBJC_SOURCE:
		case OT_OBJCXX_SOURCE:
			_makefile_print(makefile, "%s", " $(OBJDIR)");
			_makefile_print_escape(makefile, source);
			_makefile_print(makefile, "%s",
					(tt == TT_LIBTOOL) ? ".lo" : ".o");
			break;
		case OT_GOLANG_SOURCE:
			break;
		case OT_JAVA_SOURCE:
			_makefile_print(makefile, "%s", " $(OBJDIR)");
			_makefile_print_escape(makefile, source);
			_makefile_print(makefile, "%s", ".class");
			break;
		case OT_VERILOG_SOURCE:
			_makefile_print(makefile, "%s", " $(OBJDIR)");
			_makefile_print_escape(makefile, source);
			_makefile_print(makefile, "%s", ".o");
			break;
		case OT_UNKNOWN:
			ret = 1;
			fprintf(stderr, "%s%s%s", PROGNAME_CONFIGURE ": ",
					source,
					": Unknown extension for source\n");
			break;
		default:
			ret = 2;
			fprintf(stderr, "%s%s%s", PROGNAME_CONFIGURE ": ",
					source,
					": Unsupported extension for source\n");
			break;
	}
	source[len] = '.';
	return ret;
}

static int _target_binary_cc(Makefile * makefile, String const * target);
static int _target_binary_golang(Makefile * makefile, String const * target);
static int _target_flags(Makefile * makefile, String const * target);
static int _target_binary(Makefile * makefile, String const * target)
{
	String const * p;

	if(_target_objs(makefile, target) != 0)
		return 1;
	if(_target_flags(makefile, target) != 0)
		return 1;
	_makefile_print(makefile, "\n");
	if((p = _makefile_get_config(makefile, target, "sources")) != NULL
			/* XXX only checks the last source */
			&& (p = source_extension(p)) != NULL
			&& source_type(p) == OT_GOLANG_SOURCE)
		return _target_binary_golang(makefile, target);
	else
		return _target_binary_cc(makefile, target);
}

static int _target_binary_cc(Makefile * makefile, String const * target)
{
	String const * p;

	/* output the binary target */
	_makefile_print(makefile, "%s", "$(OBJDIR)");
	_makefile_print_escape(makefile, target);
	_makefile_print(makefile, "%s", "$(EXEEXT): $(");
	_makefile_print_escape_variable(makefile, target);
	_makefile_print(makefile, "%s", "_OBJS)");
	if((p = _makefile_get_config(makefile, target, "depends")) != NULL
			&& _makefile_expand(makefile, p) != 0)
		return error_print(PROGNAME_CONFIGURE);
	_makefile_print(makefile, "\n");
	/* build the binary */
	_makefile_print(makefile, "%s", "\t$(CC) -o $(OBJDIR)");
	_makefile_print_escape(makefile, target);
	_makefile_print(makefile, "%s", "$(EXEEXT) $(");
	_makefile_print_escape_variable(makefile, target);
	_makefile_print(makefile, "%s", "_OBJS) $(");
	_makefile_print_escape_variable(makefile, target);
	_makefile_print(makefile, "%s", "_LDFLAGS)\n");
	return 0;
}

static int _target_binary_golang(Makefile * makefile, String const * target)
{
	String const * sources;
	String const * p;

	/* output the binary target */
	_makefile_print(makefile, "%s", "$(OBJDIR)");
	_makefile_print_escape(makefile, target);
	_makefile_print(makefile, "%s", "$(EXEEXT):");
	if((sources = _makefile_get_config(makefile, target, "sources")) != NULL
			&& _makefile_expand(makefile, sources) != 0)
		return error_print(PROGNAME_CONFIGURE);
	if((p = _makefile_get_config(makefile, target, "depends")) != NULL
			&& _makefile_expand(makefile, p) != 0)
		return error_print(PROGNAME_CONFIGURE);
	_makefile_print(makefile, "%s", "\n");
	/* build the binary */
	_makefile_print(makefile, "%s", "\t$(GO) build $(");
	_makefile_print_escape_variable(makefile, target);
	_makefile_print(makefile, "%s", "_GOFLAGS) -o $(OBJDIR)");
	_makefile_print_escape(makefile, target);
	_makefile_print(makefile, "%s", "$(EXEEXT)");
	if(sources != NULL)
		_makefile_expand(makefile, sources);
	_makefile_print(makefile, "%s", "\n");
	return 0;
}

static void _flags_asm(Makefile * makefile, String const * target);
static void _flags_asmpp(Makefile * makefile, String const * target);
static void _flags_c(Makefile * makefile, String const * target);
static void _flags_cxx(Makefile * makefile, String const * target);
static void _flags_golang(Makefile * makefile, String const * target);
static void _flags_java(Makefile * makefile, String const * target);
static void _flags_verilog(Makefile * makefile, String const * target);
static int _target_flags(Makefile * makefile, String const * target)
{
	char done[OT_COUNT];
	String const * p;
	String * sources;
	String * q;
	String const * extension;
	ObjectType type;
	char c;
	size_t i;

	memset(&done, 0, sizeof(done));
	if((p = _makefile_get_config(makefile, target, "sources")) == NULL
			|| string_get_length(p) == 0)
	{
		if((p = _makefile_get_config(makefile, target, "type")) != NULL
				&& string_compare(p, "binary") == 0)
			_flags_c(makefile, target);
		return 0;
	}
	if((sources = string_new(p)) == NULL)
		return 1;
	q = sources;
	for(i = 0;; i++)
	{
		if(sources[i] != ',' && sources[i] != '\0')
			continue;
		c = sources[i];
		sources[i] = '\0';
		extension = source_extension(sources);
		if(extension == NULL)
		{
			sources[i] = c;
			continue;
		}
		type = source_type(extension);
		if(!done[type])
		{
			if(type != OT_UNKNOWN)
				done[type] = 1;
			switch(type)
			{
				case OT_ASMPP_SOURCE:
					_flags_asmpp(makefile, target);
					if(done[OT_ASM_SOURCE])
						break;
					/* fallthrough */
				case OT_ASM_SOURCE:
					_flags_asm(makefile, target);
					break;
				case OT_JAVA_SOURCE:
					_flags_java(makefile, target);
					break;
				case OT_OBJC_SOURCE:
					done[OT_C_SOURCE] = 1;
					/* fallthrough */
				case OT_C_SOURCE:
					_flags_c(makefile, target);
					break;
				case OT_OBJCXX_SOURCE:
					done[OT_CXX_SOURCE] = 1;
					/* fallthrough */
				case OT_CXX_SOURCE:
					_flags_cxx(makefile, target);
					break;
				case OT_GOLANG_SOURCE:
					_flags_golang(makefile, target);
					break;
				case OT_VERILOG_SOURCE:
					done[OT_VERILOG_SOURCE] = 1;
					_flags_verilog(makefile, target);
					break;
				case OT_UNKNOWN:
					break;
			}
		}
		if(c == '\0')
			break;
		sources += i + 1;
		i = 0;
	}
	string_delete(q);
	return 0;
}

static void _flags_asm(Makefile * makefile, String const * target)
{
	String const * p;

	_makefile_print(makefile, "%s%s", target, "_ASFLAGS = $(ASFLAGSF)"
			" $(ASFLAGS)");
	if((p = _makefile_get_config(makefile, target, "asflags")) != NULL)
		_makefile_print(makefile, " %s", p);
	_makefile_print(makefile, "\n");
	_makefile_print_escape_variable(makefile, target);
	_makefile_print(makefile, "%s", "_LDFLAGS = $(LDFLAGSF) $(LDFLAGS)");
	if((p = _makefile_get_config(makefile, target, "ldflags")) != NULL)
		_binary_ldflags(makefile, p);
	_makefile_print(makefile, "\n");
}

static void _flags_asmpp(Makefile * makefile, String const * target)
{
	String const * p;

	_makefile_print(makefile, "%s%s", target, "_CPPFLAGS = $(CPPFLAGSF)"
			" $(CPPFLAGS)");
	if((p = _makefile_get_config(makefile, target, "cppflags")) != NULL)
		_makefile_print(makefile, " %s", p);
	_makefile_print(makefile, "\n");
}

static void _flags_c(Makefile * makefile, String const * target)
{
	String const * p;

	_makefile_print_escape_variable(makefile, target);
	_makefile_print(makefile, "%s", "_CFLAGS = $(CPPFLAGSF) $(CPPFLAGS)");
	if((p = _makefile_get_config(makefile, target, "cppflags")) != NULL)
		_makefile_print(makefile, " %s", p);
	_makefile_print(makefile, "%s", " $(CFLAGSF) $(CFLAGS)");
	if((p = _makefile_get_config(makefile, target, "cflags")) != NULL)
	{
		_makefile_print(makefile, " %s", p);
		if(configure_get_os(makefile->configure) == HO_GNU_LINUX
				&& string_find(p, "-ansi"))
			_makefile_print(makefile, "%s", " -D _GNU_SOURCE");
	}
	_makefile_print(makefile, "\n");
	_makefile_print_escape_variable(makefile, target);
	_makefile_print(makefile, "%s", "_LDFLAGS = $(LDFLAGSF) $(LDFLAGS)");
	if((p = _makefile_get_config(makefile, target, "ldflags")) != NULL)
		_binary_ldflags(makefile, p);
	_makefile_print(makefile, "\n");
}

static void _flags_cxx(Makefile * makefile, String const * target)
{
	String const * p;

	_makefile_print_escape_variable(makefile, target);
	_makefile_print(makefile, "%s", "_CXXFLAGS = $(CPPFLAGSF) $(CPPFLAGS)");
	if((p = _makefile_get_config(makefile, target, "cppflags")) != NULL)
		_makefile_print(makefile, " %s", p);
	_makefile_print(makefile, "%s", " $(CXXFLAGSF) $(CXXFLAGS)");
	if((p = _makefile_get_config(makefile, target, "cxxflags")) != NULL)
		_makefile_print(makefile, " %s", p);
	_makefile_print(makefile, "\n");
	_makefile_print_escape_variable(makefile, target);
	_makefile_print(makefile, "%s", "_LDFLAGS = $(LDFLAGSF) $(LDFLAGS)");
	if((p = _makefile_get_config(makefile, target, "ldflags")) != NULL)
		_binary_ldflags(makefile, p);
	_makefile_print(makefile, "\n");
}

static void _flags_golang(Makefile * makefile, String const * target)
{
	String const * p;

	_makefile_print_escape_variable(makefile, target);
	_makefile_print(makefile, "%s", "_GOFLAGS = $(GOFLAGSF) $(GOFLAGS)");
	if((p = _makefile_get_config(makefile, target, "goflags")) != NULL)
		_makefile_print(makefile, " %s", p);
	_makefile_print(makefile, "\n");
}

static void _flags_java(Makefile * makefile, String const * target)
{
	String const * p;

	_makefile_print_escape_variable(makefile, target);
	_makefile_print(makefile, "%s", "_JFLAGS = $(JFLAGSF) $(JFLAGS)");
	if((p = _makefile_get_config(makefile, target, "jflags")) != NULL)
		_makefile_print(makefile, " %s", p);
	_makefile_print(makefile, "\n");
}

static void _flags_verilog(Makefile * makefile, String const * target)
{
	String const * p;

	_makefile_print_escape_variable(makefile, target);
	_makefile_print(makefile, "%s",
			"_VFLAGS = $(VFLAGSF) $(VFLAGS)");
	if((p = _makefile_get_config(makefile, target, "vflags")) != NULL)
		_makefile_print(makefile, " %s", p);
	_makefile_print(makefile, "\n");
}

static void _command_security(Makefile * makefile, String const * target,
		String const * command);
static int _target_command(Makefile * makefile, String const * target)
{
	String const * p;
	int phony;

	phony = _makefile_is_phony(makefile, target);
	_makefile_print(makefile, "\n%s", phony ? "" : "$(OBJDIR)");
	_makefile_print_escape(makefile, target);
	_makefile_print(makefile, ":");
	if((p = _makefile_get_config(makefile, target, "depends")) != NULL
			&& _makefile_expand(makefile, p) != 0)
		return error_print(PROGNAME_CONFIGURE);
	if((p = _makefile_get_config(makefile, target, "command")) == NULL)
		return error_print(PROGNAME_CONFIGURE);
	if(_makefile_is_flag_set(makefile, PREFS_S))
		_command_security(makefile, target, p);
	_makefile_print(makefile, "\n\t%s\n", p);
	return 0;
}

static void _command_security(Makefile * makefile, String const * target,
		String const * command)
{
	(void) makefile;

	error_set_print(PROGNAME_CONFIGURE, 0, "%s: %s%s%s", target,
			"Command \"", command,
			"\" is executed while compiling");
}

static int _target_library(Makefile * makefile, String const * target)
{
	String const * p;
	String * q;
	String * soname;
	HostOS os;

	if(_target_objs(makefile, target) != 0)
		return 1;
	if(_target_flags(makefile, target) != 0)
		return 1;
	if(configure_can_library_static(makefile->configure)
			/* generate a static library */
			&& _target_library_static(makefile, target) != 0)
		return 1;
	os = configure_get_os(makefile->configure);
	if((p = _makefile_get_config(makefile, target, "soname")) != NULL)
		soname = string_new(p);
	else if(os == HO_DARWIN)
		/* versioning is different on Darwin/macOS */
		soname = string_new_append(target, ".0.0$(SOEXT)", NULL);
	else if(os == HO_WIN32)
		/* and on Windows */
		soname = string_new_append(target, "$(SOEXT)", NULL);
	else
		soname = string_new_append(target, "$(SOEXT)", ".0", NULL);
	if(soname == NULL)
		return 1;
	if(os == HO_DARWIN || os == HO_WIN32)
	{
		_makefile_print(makefile, "%s", "\n$(OBJDIR)");
		_makefile_print_escape(makefile, soname);
	}
	else
	{
		_makefile_print(makefile, "%s", "\n$(OBJDIR)");
		_makefile_print_escape(makefile, soname);
		_makefile_print(makefile, "%s", ".0");
	}
	_makefile_print(makefile, ": $(");
	_makefile_print_escape_variable(makefile, target);
	_makefile_print(makefile, "%s", "_OBJS)");
	if((p = _makefile_get_config(makefile, target, "depends")) != NULL
			&& _makefile_expand(makefile, p) != 0)
		return error_print(PROGNAME_CONFIGURE);
	_makefile_print(makefile, "\n");
	/* build the shared library */
	_makefile_print(makefile, "%s", "\t$(CCSHARED) -o $(OBJDIR)");
	_makefile_print_escape(makefile, soname);
	_makefile_print(makefile, "%s", (os != HO_DARWIN && os != HO_WIN32)
			? ".0" : "");
	if((p = _makefile_get_config(makefile, target, "install")) != NULL)
	{
		/* soname is not available on Darwin/macOS */
		if(os == HO_DARWIN)
		{
			_makefile_print(makefile, "%s", " -install_name ");
			_makefile_print_escape(makefile, p);
			_makefile_print(makefile, "/");
			_makefile_print_escape(makefile, target);
			_makefile_print(makefile, ".0$(SOEXT)");
		}
		else if(os != HO_WIN32)
		{
			_makefile_print(makefile, "%s", " -Wl,-soname,");
			_makefile_print_escape(makefile, soname);
		}
	}
	_makefile_print(makefile, "%s", " $(");
	_makefile_print_escape_variable(makefile, target);
	_makefile_print(makefile, "%s", "_OBJS) $(");
	_makefile_print_escape_variable(makefile, target);
	_makefile_print(makefile, "%s", "_LDFLAGS)");
	if((q = string_new_append(target, "$(SOEXT)", NULL)) == NULL)
	{
		string_delete(soname);
		return 1;
	}
	if((p = _makefile_get_config(makefile, q, "ldflags")) != NULL)
		_binary_ldflags(makefile, p);
	string_delete(q);
	_makefile_print(makefile, "\n");
	if(os == HO_DARWIN)
	{
		_makefile_print(makefile, "%s", "\n$(OBJDIR)");
		_makefile_print_escape(makefile, target);
		_makefile_print(makefile, "%s", ".0$(SOEXT): $(OBJDIR)");
		_makefile_print_escape(makefile, soname);
		_makefile_print(makefile, "\n");
		_makefile_print(makefile, "%s", "\t$(LN) -s -- ");
		_makefile_print_escape(makefile, soname);
		_makefile_print(makefile, " $(OBJDIR)");
		_makefile_print_escape(makefile, target);
		_makefile_print(makefile, "%s", ".0$(SOEXT)\n");
		_makefile_print(makefile, "%s", "\n$(OBJDIR)");
		_makefile_print_escape(makefile, target);
		_makefile_print(makefile, "%s", "$(SOEXT): $(OBJDIR)");
		_makefile_print_escape(makefile, soname);
		_makefile_print(makefile, "\n");
		_makefile_print(makefile, "%s", "\t$(LN) -s -- ");
		_makefile_print_escape(makefile, soname);
		_makefile_print(makefile, "%s", " $(OBJDIR)");
		_makefile_print_escape(makefile, target);
		_makefile_print(makefile, "%s", "$(SOEXT)\n");
	}
	else if(os != HO_WIN32)
	{
		_makefile_print(makefile, "%s", "\n$(OBJDIR)");
		_makefile_print_escape(makefile, soname);
		_makefile_print(makefile, "%s", ": $(OBJDIR)");
		_makefile_print_escape(makefile, soname);
		_makefile_print(makefile, "%s", ".0\n");
		_makefile_print(makefile, "%s", "\t$(LN) -s -- ");
		_makefile_print_escape(makefile, soname);
		_makefile_print(makefile, "%s", ".0 $(OBJDIR)");
		_makefile_print_escape(makefile, soname);
		_makefile_print(makefile, "%s", "\n\n$(OBJDIR)");
		_makefile_print_escape(makefile, target);
		_makefile_print(makefile, "%s", "$(SOEXT): $(OBJDIR)");
		_makefile_print_escape(makefile, soname);
		_makefile_print(makefile, "%s", ".0\n");
		_makefile_print(makefile, "%s", "\t$(LN) -s -- ");
		_makefile_print_escape(makefile, soname);
		_makefile_print(makefile, "%s", ".0 $(OBJDIR)");
		_makefile_print_escape(makefile, target);
		_makefile_print(makefile, "%s", "$(SOEXT)\n");
	}
	string_delete(soname);
	return 0;
}

static int _target_library_static(Makefile * makefile, String const * target)
{
	String const * p;
	String * q;
	size_t len;

	_makefile_print(makefile, "%s", "\n$(OBJDIR)");
	_makefile_print_escape(makefile, target);
	_makefile_print(makefile, "%s", ".a: $(");
	_makefile_print_escape_variable(makefile, target);
	_makefile_print(makefile, "%s", "_OBJS)");
	if((p = _makefile_get_config(makefile, target, "depends")) != NULL
			&& _makefile_expand(makefile, p) != 0)
		return error_print(PROGNAME_CONFIGURE);
	_makefile_print(makefile, "\n");
	/* build the static library */
	_makefile_print(makefile, "%s", "\t$(AR) $(ARFLAGS) $(OBJDIR)");
	_makefile_print_escape(makefile, target);
	_makefile_print(makefile, "%s", ".a $(");
	_makefile_print_escape_variable(makefile, target);
	_makefile_print(makefile, "%s", "_OBJS)");
	len = strlen(target) + 3;
	if((q = malloc(len)) == NULL)
		return 1;
	snprintf(q, len, "%s.a", target);
	if((p = _makefile_get_config(makefile, q, "ldflags")) != NULL)
		_binary_ldflags(makefile, p);
	free(q);
	_makefile_print(makefile, "%s", "\n\t$(RANLIB) $(OBJDIR)");
	_makefile_print_escape(makefile, target);
	_makefile_print(makefile, "%s", ".a\n");
	return 0;
}

static int _target_libtool(Makefile * makefile, String const * target)
{
	String const * p;

	if(_target_objs(makefile, target) != 0)
		return 1;
	if(_target_flags(makefile, target) != 0)
		return 1;
	_makefile_print(makefile, "%s", "\n$(OBJDIR)");
	_makefile_print_escape(makefile, target);
	_makefile_print(makefile, ".la: $(");
	_makefile_print_escape_variable(makefile, target);
	_makefile_print(makefile, "%s", "_OBJS)\n");
	_makefile_print(makefile, "%s",
			"\t$(LIBTOOL) --mode=link --tag=CC $(CC) -o $(OBJDIR)");
	_makefile_print_escape(makefile, target);
	_makefile_print(makefile, "%s", ".la $(");
	_makefile_print_escape_variable(makefile, target);
	_makefile_print(makefile, "%s", "_OBJS)");
	if((p = _makefile_get_config(makefile, target, "ldflags")) != NULL)
		_binary_ldflags(makefile, p);
	_makefile_print(makefile, "%s", " -rpath $(LIBDIR) $(");
	_makefile_print_escape_variable(makefile, target);
	_makefile_print(makefile, "%s", "_LDFLAGS)\n");
	return 0;
}

static int _target_object(Makefile * makefile, String const * target)
{
	String const * p;
	String const * extension;

	if((p = _makefile_get_config(makefile, target, "sources")) == NULL)
	{
		fprintf(stderr, "%s%s%s", PROGNAME_CONFIGURE ": ", target,
				": No sources for target\n");
		return 1;
	}
	if(strchr(p, ',') != NULL)
	{
		fprintf(stderr, "%s%s%s", PROGNAME_CONFIGURE ": ", target,
				": An object can have only one source file\n");
		return 1;
	}
	if((extension = source_extension(p)) == NULL)
		return 1;
	switch(source_type(extension))
	{
		case OT_ASM_SOURCE:
			_makefile_print(makefile, "\n%s%s",
					target, "_OBJS = $(OBJDIR)");
			_makefile_print_escape(makefile, target);
			_makefile_print(makefile, "\n%s%s", target, "_ASFLAGS ="
					" $(ASFLAGSF) $(ASFLAGS)");
			if((p = _makefile_get_config(makefile, target,
							"asflags")) != NULL)
				_makefile_print(makefile, " %s", p);
			_makefile_print(makefile, "\n");
			break;
		case OT_ASMPP_SOURCE:
			_makefile_print(makefile, "\n%s%s", target,
					"_OBJS = $(OBJDIR)");
			_makefile_print_escape(makefile, target);
			_makefile_print(makefile, "\n%s%s", target,
					"_CPPFLAGS = $(CPPFLAGSF) $(CPPFLAGS)");
			if((p = _makefile_get_config(makefile, target,
							"cppflags")) != NULL)
				_makefile_print(makefile, " %s", p);
			_makefile_print(makefile, "\n%s%s", target,
					"_ASFLAGS = $(ASFLAGSF) $(ASFLAGS)");
			if((p = _makefile_get_config(makefile, target,
							"asflags")) != NULL)
				_makefile_print(makefile, " %s", p);
			_makefile_print(makefile, "\n");
			break;
		case OT_C_SOURCE:
		case OT_OBJC_SOURCE:
			_makefile_print(makefile, "\n%s%s%s", target,
					"_OBJS = ", "$(OBJDIR)");
			_makefile_print_escape(makefile, target);
			_makefile_print(makefile, "\n%s%s", target, "_CFLAGS ="
					" $(CPPFLAGSF) $(CPPFLAGS)");
			if((p = _makefile_get_config(makefile, target,
							"cppflags")) != NULL)
				_makefile_print(makefile, " %s", p);
			_makefile_print(makefile, "%s", " $(CFLAGSF)"
					" $(CFLAGS)");
			if((p = _makefile_get_config(makefile, target,
							"cflags")) != NULL)
				_makefile_print(makefile, " %s", p);
			_makefile_print(makefile, "\n");
			break;
		case OT_CXX_SOURCE:
		case OT_OBJCXX_SOURCE:
			_makefile_print(makefile, "\n%s%s%s", target,
					"_OBJS = ", "$(OBJDIR)");
			_makefile_print_escape(makefile, target);
			_makefile_print(makefile, "\n%s%s", target,
					"_CXXFLAGS = $(CPPFLAGSF) $(CPPFLAGS)");
			if((p = _makefile_get_config(makefile, target,
							"cppflags")) != NULL)
				_makefile_print(makefile, " %s", p);
			_makefile_print(makefile, "%s", " $(CXXFLAGSF)"
					" $(CXXFLAGS)");
			if((p = _makefile_get_config(makefile, target,
							"cxxflags")) != NULL)
				_makefile_print(makefile, " %s", p);
			_makefile_print(makefile, "\n");
			break;
		case OT_GOLANG_SOURCE:
			_makefile_print(makefile, "\n%s%s", target, "_GOFLAGS ="
					" $(GOFLAGSF) $(GOFLAGS)");
			if((p = _makefile_get_config(makefile, target,
							"goflags")) != NULL)
				_makefile_print(makefile, " %s", p);
			_makefile_print(makefile, "\n");
			break;
		case OT_JAVA_SOURCE:
			_makefile_print(makefile, "\n");
			_makefile_print_escape_variable(makefile, target);
			_makefile_print(makefile, "%s", "_OBJS = $(OBJDIR)");
			_makefile_print_escape(makefile, target);
			_makefile_print(makefile, "\n");
			_makefile_print_escape_variable(makefile, target);
			_makefile_print(makefile, "%s",
					"_JFLAGS = $(JFLAGSF) $(JFLAGS)");
			if((p = _makefile_get_config(makefile, target,
							"jflags")) != NULL)
				_makefile_print(makefile, " %s", p);
			_makefile_print(makefile, "\n");
			break;
		case OT_VERILOG_SOURCE:
			_makefile_print(makefile, "\n");
			_makefile_print_escape_variable(makefile, target);
			_makefile_print(makefile, "%s", "_OBJS = $(OBJDIR)");
			_makefile_print_escape(makefile, target);
			_makefile_print(makefile, "\n");
			_makefile_print_escape_variable(makefile, target);
			_makefile_print(makefile, "%s",
					"_VFLAGS = $(VFLAGSF) $(VFLAGS)");
			if((p = _makefile_get_config(makefile, target,
							"vflags")) != NULL)
				_makefile_print(makefile, " %s", p);
			_makefile_print(makefile, "\n");
			break;
		case OT_UNKNOWN:
			fprintf(stderr, "%s%s%s", PROGNAME_CONFIGURE ": ",
					target,
					": Unknown source type for object\n");
			return 1;
	}
	return 0;
}

static int _target_plugin(Makefile * makefile, String const * target)
{
	String const * p;
	String * q;

	if(_target_objs(makefile, target) != 0)
		return 1;
	if(_target_flags(makefile, target) != 0)
		return 1;
	_makefile_print(makefile, "%s", "\n$(OBJDIR)");
	_makefile_print_escape(makefile, target);
	_makefile_print(makefile, "%s", "$(SOEXT): $(");
	_makefile_print_escape_variable(makefile, target);
	_makefile_print(makefile, "%s", "_OBJS)");
	if((p = _makefile_get_config(makefile, target, "depends")) != NULL
			&& _makefile_expand(makefile, p) != 0)
		return error_print(PROGNAME_CONFIGURE);
	_makefile_print(makefile, "\n");
	/* build the plug-in */
	_makefile_print(makefile, "%s", "\t$(CCSHARED) -o $(OBJDIR)");
	_makefile_print_escape(makefile, target);
	_makefile_print(makefile, "%s", "$(SOEXT) $(");
	_makefile_print_escape_variable(makefile, target);
	_makefile_print(makefile, "%s", "_OBJS) $(");
	_makefile_print_escape_variable(makefile, target);
	_makefile_print(makefile, "%s", "_LDFLAGS)");
	if((q = string_new_append(target, "$(SOEXT)", NULL)) == NULL)
		return error_print(PROGNAME_CONFIGURE);
	if((p = _makefile_get_config(makefile, q, "ldflags")) != NULL)
		_binary_ldflags(makefile, p);
	string_delete(q);
	_makefile_print(makefile, "\n");
	return 0;
}

static int _objects_target(Makefile * makefile,
		String const * target);
static int _write_objects(Makefile * makefile)
{
	String const * p;
	String * targets;
	String * q;
	char c;
	size_t i;
	int ret = 0;

	if((p = _makefile_get_config_mode(makefile, NULL, "targets")) == NULL)
		return 0;
	if((targets = string_new(p)) == NULL)
		return 1;
	q = targets;
	for(i = 0;; i++)
	{
		if(targets[i] != ',' && targets[i] != '\0')
			continue;
		c = targets[i];
		targets[i] = '\0';
		ret += _objects_target(makefile, targets);
		if(c == '\0')
			break;
		targets += i + 1;
		i = 0;
	}
	string_delete(q);
	return ret;
}

static void _script_check(Makefile * makefile, String const * target,
		String const * script);
static int _script_depends(Makefile * makefile, String const * target);
static String * _script_path(Makefile * makefile, String const * script);
static void _script_security(Makefile * makefile, String const * target,
		String const * script);
static int _target_script(Makefile * makefile,
		String const * target)
{
	String const * prefix;
	String const * script;
	String const * flags;
	int phony;

	if((script = _makefile_get_config(makefile, target, "script")) == NULL)
	{
		fprintf(stderr, "%s%s%s", PROGNAME_CONFIGURE ": ", target,
				": No script for target\n");
		return 1;
	}
	flags = _makefile_get_config(makefile, target, "flags");
	if(makefile->fp == NULL)
		_script_check(makefile, target, script);
	if(_makefile_is_flag_set(makefile, PREFS_S))
		_script_security(makefile, target, script);
	phony = _makefile_is_phony(makefile, target);
	_makefile_print(makefile, "\n%s", phony ? "" : "$(OBJDIR)");
	_makefile_print_escape(makefile, target);
	_makefile_print(makefile, ":");
	_script_depends(makefile, target);
	if((prefix = _makefile_get_config(makefile, target, "prefix")) == NULL)
		prefix = "$(PREFIX)";
	_makefile_print(makefile, "\n\t");
	_makefile_print_escape(makefile, script);
	_makefile_print(makefile, " -P \"%s\"", prefix);
	if(flags != NULL && flags[0] != '\0')
		_makefile_print(makefile, " %s", flags);
	_makefile_print(makefile, " -- \"%s%s\"\n", phony ? "" : "$(OBJDIR)",
			target);
	return 0;
}

static void _script_check(Makefile * makefile, String const * target,
		String const * script)
{
	String * path;

	if((path = _script_path(makefile, script)) == NULL)
	{
		error_print(PROGNAME_CONFIGURE);
		return;
	}
	/* XXX make it clear these are warnings */
	if(access(path, R_OK) != 0)
		error_set_print(PROGNAME_CONFIGURE, 0, "%s: %s%s%s%s%s", target,
				"The \"", path, "\" script is not readable (",
				strerror(errno), ")");
	else if(access(path, R_OK | X_OK) != 0)
		error_set_print(PROGNAME_CONFIGURE, 0, "%s: %s%s%s%s%s", target,
				"The \"", path, "\" script is not executable (",
				strerror(errno), ")");
	string_delete(path);
}

static int _script_depends(Makefile * makefile, String const * target)
{
	String const * p;

	if((p = _makefile_get_config(makefile, target, "depends")) != NULL
			&& _makefile_expand(makefile, p) != 0)
		return error_print(PROGNAME_CONFIGURE);
	return 0;
}

static String * _script_path(Makefile * makefile, String const * script)
{
	String * ret;
	String const * directory;
	ssize_t i;
	String * p = NULL;

	if((directory = _makefile_get_config(makefile, NULL, "_directory"))
			== NULL)
	{
		error_print(PROGNAME_CONFIGURE);
		return NULL;
	}
	/* XXX truncate scripts at the first space (to allow arguments) */
	if((i = string_index(script, " ")) > 0)
	{
		if((p = string_new_length(script, i)) == NULL)
		{
			error_print(PROGNAME_CONFIGURE);
			return NULL;
		}
		script = p;
	}
	if(script[0] == '/')
		ret = string_new(script);
	else if(string_compare_length(script, "./", 2) == 0)
		ret = string_new_append(directory, &script[1], NULL);
	else
		ret = string_new_append(directory, "/", script, NULL);
	string_delete(p);
	return ret;
}

static void _script_security(Makefile * makefile, String const * target,
		String const * script)
{
	String * path;

	if((path = _script_path(makefile, script)) == NULL)
	{
		error_print(PROGNAME_CONFIGURE);
		return;
	}
	error_set_print(PROGNAME_CONFIGURE, 0, "%s: %s%s%s", target, "The \"",
			path, "\" script is executed while compiling");
	string_delete(path);
}

static int _target_source(Makefile * makefile, String const * target,
		String * source);
static int _objects_target(Makefile * makefile,
		String const * target)
{
	int ret = 0;
	String const * p;
	String * sources;
	String * q;
	size_t i;
	char c;

	if((p = _makefile_get_config(makefile, target, "sources")) == NULL)
		return 0;
	if((sources = string_new(p)) == NULL)
		return 1;
	q = sources;
	for(i = 0;; i++)
	{
		if(sources[i] != ',' && sources[i] != '\0')
			continue;
		c = sources[i];
		sources[i] = '\0';
		ret |= _target_source(makefile, target, sources);
		if(c == '\0')
			break;
		sources += i + 1;
		i = 0;
	}
	string_delete(q);
	return ret;
}

static int _source_depends(Makefile * makefile,
		String const * source);
static int _source_subdir(Makefile * makefile, String * source);
static int _target_source(Makefile * makefile, String const * target,
		String * source)
	/* FIXME check calls to _source_depends() */
{
	int ret = 0;
	String const * extension;
	TargetType tt = TT_UNKNOWN;
	ObjectType ot;
	size_t len;
	String const * p;
	String const * q;

	if((p = _makefile_get_config(makefile, target, "type")) != NULL)
			tt = enum_string(TT_LAST, sTargetType, p);
	if((extension = source_extension(source)) == NULL)
		return 1;
	len = string_get_length(source) - string_get_length(extension) - 1;
	source[len] = '\0';
	switch((ot = source_type(extension)))
	{
		case OT_ASM_SOURCE:
		case OT_ASMPP_SOURCE:
			if(tt == TT_OBJECT)
				_makefile_print(makefile, "%s%s", "\n$(OBJDIR)",
						target);
			else
				_makefile_print(makefile, "%s%s%s",
						"\n$(OBJDIR)", source, ".o");
			if(tt == TT_LIBTOOL)
				_makefile_print(makefile, " $(OBJDIR)%s.lo",
						source);
			_makefile_print(makefile, "%s%s%s%s", ": ", source, ".",
					extension);
			source[len] = '.'; /* FIXME ugly */
			_source_depends(makefile, source);
			source[len] = '\0';
			_makefile_print(makefile, "%s", "\n\t");
			if(strchr(source, '/') != NULL)
				ret = _source_subdir(makefile, source);
			if(tt == TT_LIBTOOL)
				_makefile_print(makefile, "%s", "$(LIBTOOL)"
						" --mode=compile --tag=CC ");
			_makefile_print(makefile, "%s", "$(AS)");
			source[len] = '.'; /* FIXME ugly */
			if(ot == OT_ASMPP_SOURCE)
			{
				_makefile_print(makefile, "%s", " $(");
				_makefile_print_escape_variable(makefile,
						target);
				_makefile_print(makefile, "%s", "_CPPFLAGS)");
				if((p = _makefile_get_config(makefile, source,
								"cppflags"))
						!= NULL)
					_makefile_print(makefile, " %s", p);
			}
			_makefile_print(makefile, "%s", " $(");
			_makefile_print_escape_variable(makefile, target);
			_makefile_print(makefile, "%s", "_ASFLAGS)");
			if((p = _makefile_get_config(makefile, source,
							"asflags")) != NULL)
				_makefile_print(makefile, " %s", p);
			source[len] = '\0'; /* FIXME ugly */
			if(tt == TT_OBJECT)
			{
				_makefile_print(makefile, "%s", " -o $(OBJDIR)");
				_makefile_print_escape(makefile, target);
				_makefile_print(makefile, " ");
				_makefile_print_escape(makefile, source);
				_makefile_print(makefile, ".%s\n", extension);
			}
			else
			{
				_makefile_print(makefile, "%s", " -o $(OBJDIR)");
				_makefile_print_escape(makefile, source);
				_makefile_print(makefile, ".o ");
				_makefile_print_escape(makefile, source);
				_makefile_print(makefile, ".%s\n", extension);
			}
			break;
		case OT_C_SOURCE:
		case OT_OBJC_SOURCE:
			if(tt == TT_OBJECT)
			{
				_makefile_print(makefile, "%s", "\n$(OBJDIR)");
				_makefile_print_escape(makefile, target);
			}
			else
			{
				_makefile_print(makefile, "%s", "\n$(OBJDIR)");
				_makefile_print_escape(makefile, source);
				_makefile_print(makefile, ".o");
			}
			if(tt == TT_LIBTOOL)
			{
				_makefile_print(makefile, " $(OBJDIR)");
				_makefile_print_escape(makefile, source);
				_makefile_print(makefile, "%s", ".lo");
			}
			_makefile_print(makefile, ": ");
			_makefile_print_escape(makefile, source);
			_makefile_print(makefile, ".%s", extension);
			source[len] = '.'; /* FIXME ugly */
			_source_depends(makefile, source);
			_makefile_print(makefile, "%s", "\n\t");
			if(strchr(source, '/') != NULL)
				ret = _source_subdir(makefile, source);
			/* FIXME do both wherever also relevant */
			p = _makefile_get_config(makefile, source, "cppflags");
			q = _makefile_get_config(makefile, source, "cflags");
			source[len] = '\0';
			if(tt == TT_LIBTOOL)
				_makefile_print(makefile, "%s", "$(LIBTOOL)"
						" --mode=compile --tag=CC ");
			_makefile_print(makefile, "%s", "$(CC)");
			if(p != NULL)
				_makefile_print(makefile, " %s", p);
			_makefile_print(makefile, "%s", " $(");
			_makefile_print_escape_variable(makefile, target);
			_makefile_print(makefile, "%s", "_CFLAGS)");
			if(q != NULL)
			{
				_makefile_print(makefile, " %s", q);
				if(configure_get_os(makefile->configure)
						== HO_GNU_LINUX
					       	&& string_find(q, "-ansi"))
					_makefile_print(makefile, "%s",
							" -D _GNU_SOURCE");
			}
			if(tt == TT_OBJECT)
			{
				_makefile_print(makefile, "%s",
						" -o $(OBJDIR)");
				_makefile_print_escape(makefile, target);
			}
			else
			{
				_makefile_print(makefile, "%s",
						" -o $(OBJDIR)");
				_makefile_print_escape(makefile, source);
				_makefile_print(makefile, ".o");
			}
			_makefile_print(makefile, "%s", " -c ");
			_makefile_print_escape(makefile, source);
			_makefile_print(makefile, ".%s\n", extension);
			break;
		case OT_CXX_SOURCE:
		case OT_OBJCXX_SOURCE:
			if(tt == TT_OBJECT)
			{
				_makefile_print(makefile, "%s", "\n$(OBJDIR)");
				_makefile_print_escape(makefile, target);
			}
			else
			{
				_makefile_print(makefile, "%s", "\n$(OBJDIR)");
				_makefile_print_escape(makefile, source);
				_makefile_print(makefile, "%s", ".o");
			}
			_makefile_print(makefile, "%s%s%s%s", ": ", source, ".",
					extension);
			source[len] = '.'; /* FIXME ugly */
			_source_depends(makefile, source);
			p = _makefile_get_config(makefile, source, "cxxflags");
			source[len] = '\0';
			_makefile_print(makefile, "\n\t");
			if(strchr(source, '/') != NULL)
				ret = _source_subdir(makefile, source);
			if(tt == TT_LIBTOOL)
				_makefile_print(makefile, "%s", "$(LIBTOOL)"
						" --mode=compile --tag=CXX ");
			_makefile_print(makefile, "%s", "$(CXX) $(");
			_makefile_print_escape(makefile, target);
			_makefile_print(makefile, "%s", "_CXXFLAGS)");
			if(p != NULL)
				_makefile_print(makefile, " %s", p);
			if(tt == TT_OBJECT)
			{
				_makefile_print(makefile, "%s", " -o $(OBJDIR)");
				_makefile_print_escape(makefile, target);
			}
			else
			{
				_makefile_print(makefile, "%s", " -o $(OBJDIR)");
				_makefile_print_escape(makefile, source);
				_makefile_print(makefile, ".o");
			}
			_makefile_print(makefile, "%s", " -c ");
			_makefile_print_escape(makefile, source);
			_makefile_print(makefile, ".%s\n", extension);
			break;
		case OT_GOLANG_SOURCE:
			break;
		case OT_JAVA_SOURCE:
			_makefile_print(makefile, "%s", "\n$(OBJDIR)");
			_makefile_print_escape(makefile, target);
			_makefile_print(makefile, ": ");
			_makefile_print_escape(makefile, source);
			_makefile_print(makefile, ".%s", extension);
			source[len] = '.'; /* FIXME ugly */
			_source_depends(makefile, source);
			_makefile_print(makefile, "\n\t");
			if(strchr(source, '/') != NULL)
				ret = _source_subdir(makefile, source);
			q = _makefile_get_config(makefile, source, "jflags");
			source[len] = '\0';
			_makefile_print(makefile, "%s", "$(JAVAC) $(");
			_makefile_print_escape_variable(makefile, target);
			_makefile_print(makefile, "%s", "_JFLAGS)");
			if(q != NULL)
				_makefile_print(makefile, " %s", q);
			_makefile_print(makefile, " ");
			_makefile_print_escape(makefile, source);
			_makefile_print(makefile, ".%s\n", extension);
			break;
		case OT_VERILOG_SOURCE:
			if(tt == TT_OBJECT)
			{
				_makefile_print(makefile, "%s", "\n$(OBJDIR)");
				_makefile_print_escape(makefile, target);
			}
			else
			{
				_makefile_print(makefile, "%s", "\n$(OBJDIR)");
				_makefile_print_escape(makefile, source);
				_makefile_print(makefile, ".o");
			}
			_makefile_print(makefile, ": ");
			_makefile_print_escape(makefile, source);
			_makefile_print(makefile, ".%s", extension);
			source[len] = '.'; /* FIXME ugly */
			_source_depends(makefile, source);
			_makefile_print(makefile, "\n\t");
			if(strchr(source, '/') != NULL)
				ret = _source_subdir(makefile, source);
			q = _makefile_get_config(makefile, source, "vflags");
			source[len] = '\0';
			_makefile_print(makefile, "%s", "$(VERILOG) $(");
			_makefile_print_escape_variable(makefile, target);
			_makefile_print(makefile, "%s", "_VFLAGS)");
			if(q != NULL)
				_makefile_print(makefile, " %s", q);
			if(tt == TT_OBJECT)
			{
				_makefile_print(makefile, "%s",
						" -o $(OBJDIR)");
				_makefile_print_escape(makefile, target);
			}
			else
			{
				_makefile_print(makefile, "%s",
						" -o $(OBJDIR)");
				_makefile_print_escape(makefile, source);
				_makefile_print(makefile, ".o");
			}
			_makefile_print(makefile, " ");
			_makefile_print_escape(makefile, source);
			_makefile_print(makefile, "%s%s\n", ".", extension);
			break;
		case OT_UNKNOWN:
			fprintf(stderr, "%s%s%s", PROGNAME_CONFIGURE ": ",
					target,
					": Unknown source type for object\n");
			ret = 1;
			break;
		default:
			fprintf(stderr, "%s%s%s", PROGNAME_CONFIGURE ": ",
					target,
					": Unsupported source type for"
					" object\n");
			ret = 1;
			break;
	}
	source[len] = '.';
	return ret;
}

static int _source_depends(Makefile * makefile,
		String const * target)
{
	String const * p;

	if((p = _makefile_get_config(makefile, target, "depends")) != NULL
			&& _makefile_expand(makefile, p) != 0)
		return error_print(PROGNAME_CONFIGURE);
	return 0;
}

static int _source_subdir(Makefile * makefile, String * source)
{
	char * p;
	char const * q;

	if((p = strdup(source)) == NULL)
		return 1;
	q = dirname(p);
	_makefile_print(makefile,
			"@[ -d \"%s%s\" ] || $(MKDIR) -- \"%s%s\"\n\t",
			"$(OBJDIR)", q, "$(OBJDIR)", q);
	free(p);
	return 0;
}

static int _clean_targets(Makefile * makefile);
static int _write_clean(Makefile * makefile)
{
	_makefile_target(makefile, "clean", NULL);
	if(_makefile_get_config_mode(makefile, NULL, "subdirs") != NULL)
		_makefile_subdirs(makefile, "clean");
	return _clean_targets(makefile);
}

static int _clean_targets(Makefile * makefile)
{
	String const * prefix;
	String const * flags;
	String const * p;
	String * targets;
	String * q;
	size_t cnt;
	size_t i;
	char c;
	int phony;

	if((p = _makefile_get_config_mode(makefile, NULL, "targets")) == NULL)
		return 0;
	if((targets = string_new(p)) == NULL)
		return 1;
	q = targets;
	/* remove all of the object files */
	for(i = 0, cnt = 0;; i++)
	{
		if(targets[i] != ',' && targets[i] != '\0')
			continue;
		c = targets[i];
		targets[i] = '\0';
		if(_makefile_get_type(makefile, targets) != TT_COMMAND
				&& _makefile_get_type(makefile, targets)
				!= TT_SCRIPT)
		{
			if(cnt++ == 0)
				_makefile_print(makefile, "%s", "\t$(RM) --");
			_makefile_print(makefile, "%s", " $(");
			_makefile_print_escape_variable(makefile, targets);
			_makefile_print(makefile, "%s", "_OBJS)");
		}
		if(c == '\0')
			break;
		targets[i] = c;
		targets += i + 1;
		i = 0;
	}
	if(cnt > 0)
		_makefile_print(makefile, "\n");
	targets = q;
	/* let each scripted target remove the relevant object files */
	for(i = 0;; i++)
	{
		if(targets[i] != ',' && targets[i] != '\0')
			continue;
		c = targets[i];
		targets[i] = '\0';
		if((p = _makefile_get_config(makefile, targets, "type")) != NULL
				&& strcmp(p, "script") == 0
				&& (p = _makefile_get_config(makefile, targets,
						"script")) != NULL)
		{
			if((prefix = _makefile_get_config(makefile, targets,
							"prefix")) == NULL)
				prefix = "$(PREFIX)";
			flags = _makefile_get_config(makefile, targets,
					"flags");
			phony = _makefile_is_phony(makefile, targets);
			_makefile_print(makefile, "\t");
			_makefile_print_escape(makefile, p);
			_makefile_print(makefile, "%s%s%s", " -c -P \"", prefix,
					"\"");
			if(flags != NULL && flags[0] != '\0')
				_makefile_print(makefile, " %s", flags);
			_makefile_print(makefile, "%s%s%s%s", " -- \"",
					phony ? "" : "$(OBJDIR)",
					targets, "\"\n");
		}
		if(c == '\0')
			break;
		targets[i] = c;
		targets += i + 1;
		i = 0;
	}
	string_delete(q);
	return 0;
}

static int _write_distclean(Makefile * makefile)
{
	String const * subdirs;
	String const * p;
	String * targets;
	String * q;
	size_t cnt;
	size_t i;
	char c;
	int phony = 0;

	/* only depend on the "clean" target if we do not have subfolders */
	if((subdirs = _makefile_get_config_mode(makefile, NULL, "subdirs"))
			== NULL)
		_makefile_target(makefile, "distclean", "clean", NULL);
	else
	{
		_makefile_target(makefile, "distclean", NULL);
		_makefile_subdirs(makefile, "distclean");
		_clean_targets(makefile);
	}
	/* XXX do not erase phony targets */
	if((p = _makefile_get_config_mode(makefile, NULL, "targets")) == NULL)
		return 0;
	if((targets = string_new(p)) == NULL)
		return 1;
	q = targets;
	for(i = 0, cnt = 0;; i++)
	{
		if(targets[i] != ',' && targets[i] != '\0')
			continue;
		c = targets[i];
		targets[i] = '\0';
		if(_makefile_is_phony(makefile, targets))
			phony = 1;
		else
			cnt++;
		if(c == '\0')
			break;
		targets[i] = c;
		targets += i + 1;
		i = 0;
	}
	/* FIXME do not erase targets that need be distributed */
	if(!phony)
		_makefile_remove(makefile, 0, "$(TARGETS)", NULL);
	else if(cnt > 0)
	{
		targets = q;
		for(i = 0, cnt = 0;; i++)
		{
			if(targets[i] != ',' && targets[i] != '\0')
				continue;
			c = targets[i];
			targets[i] = '\0';
			if(!_makefile_is_phony(makefile, targets))
			{
				if(cnt++ == 0)
					_makefile_print(makefile, "\t$(RM) --");
				_makefile_print(makefile, "%s", " $(OBJDIR)");
				_makefile_print_escape(makefile, targets);
			}
			if(c == '\0')
				break;
			targets[i] = c;
			targets += i + 1;
			i = 0;
		}
		_makefile_print(makefile, "\n");
	}
	string_delete(q);
	return 0;
}

static int _dist_subdir(Makefile * makefile, Config * subdir);
static int _write_dist(Makefile * makefile, configArray * ca, int from, int to)
{
	String const * package;
	String const * version;
	Config * p;
	int i;

	package = _makefile_get_config_mode(makefile, NULL, "package");
	version = _makefile_get_config_mode(makefile, NULL, "version");
	if(package == NULL || version == NULL)
		return 0;
	_makefile_target(makefile, "dist", NULL);
	_makefile_remove(makefile, 1, "$(OBJDIR)$(PACKAGE)-$(VERSION)", NULL);
	_makefile_link(makefile, 1, "\"$$PWD\"",
			"$(OBJDIR)$(PACKAGE)-$(VERSION)");
	_makefile_print(makefile, "%s", "\t@cd $(OBJDIR). && $(TAR) -czvf"
			" $(PACKAGE)-$(VERSION)$(TGZEXT) -- \\\n");
	for(i = from + 1; i < to; i++)
	{
		array_get_copy(ca, i, &p);
		_dist_subdir(makefile, p);
	}
	if(from < to)
	{
		array_get_copy(ca, from, &p);
		_dist_subdir(makefile, p);
	}
	else
		return 1;
	_makefile_remove(makefile, 0, "$(OBJDIR)$(PACKAGE)-$(VERSION)", NULL);
	return 0;
}

static int _write_distcheck(Makefile * makefile)
{
	String const * package;
	String const * version;
	const char pretarget[] = "\ndistcheck: dist\n"
		"\t$(TAR) -xzvf $(OBJDIR)$(PACKAGE)-$(VERSION)$(TGZEXT)\n"
		"\t$(MKDIR) -- $(PACKAGE)-$(VERSION)/objdir\n"
		"\t$(MKDIR) -- $(PACKAGE)-$(VERSION)/destdir\n";
	const char target[] = "\tcd \"$(PACKAGE)-$(VERSION)\" && $(MAKE) OBJDIR=\"$$PWD/objdir/\"\n"
		"\tcd \"$(PACKAGE)-$(VERSION)\" && $(MAKE) OBJDIR=\"$$PWD/objdir/\" DESTDIR=\"$$PWD/destdir\" install\n"
		"\tcd \"$(PACKAGE)-$(VERSION)\" && $(MAKE) OBJDIR=\"$$PWD/objdir/\" DESTDIR=\"$$PWD/destdir\" uninstall\n"
		"\tcd \"$(PACKAGE)-$(VERSION)\" && $(MAKE) OBJDIR=\"$$PWD/objdir/\" distclean\n"
		"\tcd \"$(PACKAGE)-$(VERSION)\" && $(MAKE) dist\n";
	const char posttarget[] = "\t$(RM) -r -- $(PACKAGE)-$(VERSION)\n";

	package = _makefile_get_config_mode(makefile, NULL, "package");
	version = _makefile_get_config_mode(makefile, NULL, "version");
	if(package == NULL || version == NULL)
		return 0;
	_makefile_print(makefile, "%s%s%s", pretarget, target, posttarget);
	return 0;
}

static int _dist_subdir_dist(Makefile * makefile, String const * path,
		String const * dist);
static int _dist_subdir(Makefile * makefile, Config * subdir)
{
	String const * path;
	size_t len;
	String const * p;
	String * targets;
	String * q;
	String const * includes;
	String const * dist;
	size_t i;
	char c;
	String const * quote;

	path = _makefile_get_config(makefile, NULL, "_directory");
	len = (path != NULL) ? string_get_length(path) : 0;
	if((path = config_get(subdir, NULL, "_directory")) == NULL)
		path = "";
	path = &path[len];
	if(path[0] == '/')
		path++;
	if((p = config_get(subdir, NULL, "targets")) != NULL)
	{
		/* FIXME unique SOURCES */
		if((targets = string_new(p)) == NULL)
			return 1;
		q = targets;
		for(i = 0;; i++)
		{
			if(targets[i] != ',' && targets[i] != '\0')
				continue;
			c = targets[i];
			targets[i] = '\0';
			if((dist = config_get(subdir, targets, "sources"))
					!= NULL)
				_dist_subdir_dist(makefile, path, dist);
			if(c == '\0')
				break;
			targets += i + 1;
			i = 0;
		}
		string_delete(q);
	}
	if((includes = config_get(subdir, NULL, "includes")) != NULL)
		_dist_subdir_dist(makefile, path, includes);
	if((dist = config_get(subdir, NULL, "dist")) != NULL)
		_dist_subdir_dist(makefile, path, dist);
	quote = (strchr(path, ' ') != NULL) ? "\"" : "";
	_makefile_print(makefile, "%s%s%s%s%s%s%s%s", "\t\t", quote,
			"$(PACKAGE)-$(VERSION)/", path,
			(path[0] == '\0') ? "" : "/", PROJECT_CONF, quote,
			(path[0] == '\0') ? "\n" : " \\\n");
	return 0;
}

static int _dist_subdir_dist(Makefile * makefile, String const * path,
		String const * dist)
{
	String * d;
	String * p;
	size_t i;
	char c;
	String const * quote;

	if((d = string_new(dist)) == NULL)
		return 1;
	p = d;
	for(i = 0;; i++)
	{
		if(d[i] != ',' && d[i] != '\0')
			continue;
		c = d[i];
		d[i] = '\0';
		quote = (strchr(path, ' ') != NULL || strchr(d, ' ') != NULL)
			? "\"" : "";
		_makefile_print(makefile, "%s%s%s%s%s%s%s%s", "\t\t", quote,
				"$(PACKAGE)-$(VERSION)/",
				(path[0] == '\0') ? "" : path,
				(path[0] == '\0') ? "" : "/",
				d, quote, " \\\n");
		if(c == '\0')
			break;
		d[i] = c;
		d += i + 1;
		i = 0;
	}
	string_delete(p);
	return 0;
}

static int _install_targets(Makefile * makefile);
static int _install_includes(Makefile * makefile);
static int _install_dist(Makefile * makefile);
static int _write_install(Makefile * makefile)
{
	int ret = 0;

	_makefile_target(makefile, "install", "all", NULL);
	if(_makefile_get_config_mode(makefile, NULL, "subdirs") != NULL)
		_makefile_subdirs(makefile, "install");
	ret |= _install_targets(makefile);
	ret |= _install_includes(makefile);
	ret |= _install_dist(makefile);
	return ret;
}

static int _install_target(Makefile * makefile, String const * target);
static int _install_targets(Makefile * makefile)
{
	int ret = 0;
	String const * p;
	String * q;
	String * targets;
	size_t i;
	char c;

	if((p = _makefile_get_config_mode(makefile, NULL, "targets")) == NULL)
		return 0;
	if((targets = string_new(p)) == NULL)
		return 1;
	q = targets;
	for(i = 0; ret == 0; i++)
	{
		if(targets[i] != ',' && targets[i] != '\0')
			continue;
		c = targets[i];
		targets[i] = '\0';
		if(_makefile_is_enabled(makefile, targets) != 0)
			ret |= _install_target(makefile, targets);
		if(c == '\0')
			break;
		targets += i + 1;
		i = 0;
	}
	string_delete(q);
	return ret;
}

static void _install_target_binary(Makefile * makefile, String const * target);
static void _install_target_command(Makefile * makefile, String const * target);
static int _install_target_library(Makefile * makefile, String const * target);
static void _install_target_libtool(Makefile * makefile, String const * target);
static void _install_target_object(Makefile * makefile, String const * target);
static void _install_target_plugin(Makefile * makefile, String const * target);
static void _install_target_script(Makefile * makefile, String const * target);
static int _install_target(Makefile * makefile, String const * target)
{
	int ret = 0;
	String const * type;
	TargetType tt;

	if((type = _makefile_get_config(makefile, target, "type")) == NULL)
		return 1;
	switch((tt = enum_string(TT_LAST, sTargetType, type)))
	{
		case TT_BINARY:
			_install_target_binary(makefile, target);
			break;
		case TT_COMMAND:
			_install_target_command(makefile, target);
			break;
		case TT_LIBRARY:
			ret = _install_target_library(makefile, target);
			break;
		case TT_LIBTOOL:
			_install_target_libtool(makefile, target);
			break;
		case TT_OBJECT:
			_install_target_object(makefile, target);
			break;
		case TT_PLUGIN:
			_install_target_plugin(makefile, target);
			break;
		case TT_SCRIPT:
			_install_target_script(makefile, target);
			break;
		case TT_UNKNOWN:
			break;
	}
	return ret;
}

static void _install_target_command(Makefile * makefile, String const * target)
{
	String const * path;
	String const * mode;
	mode_t m = 0644;
	String * p;

	if((path = _makefile_get_config(makefile, target, "install")) == NULL)
		return;
	if(_makefile_is_phony(makefile, target))
		return;
	if((mode = _makefile_get_config(makefile, target, "mode")) == NULL
			/* XXX these tests are not sufficient */
			|| mode[0] == '\0'
			|| (m = strtol(mode, &p, 8)) == 0
			|| *p != '\0')
		mode = "0644";
	_makefile_mkdir(makefile, path);
	_makefile_print(makefile, "%s%s%s", "\t$(INSTALL) -m ", mode,
			" $(OBJDIR)");
	_makefile_print_escape(makefile, target);
	_makefile_print(makefile, "%s%s", " $(DESTDIR)", path);
	_makefile_print(makefile, "/");
	_makefile_print_escape(makefile, target);
	_makefile_print(makefile, "\n");
}

static void _install_target_binary(Makefile * makefile, String const * target)
{
	String const * path;

	if((path = _makefile_get_config(makefile, target, "install")) == NULL)
		return;
	_makefile_mkdir(makefile, path);
	_makefile_print(makefile, "%s", "\t$(INSTALL) -m 0755 $(OBJDIR)");
	_makefile_print_escape(makefile, target);
	_makefile_print(makefile, "%s", "$(EXEEXT) $(DESTDIR)");
	_makefile_print_escape(makefile, path);
	_makefile_print(makefile, "/");
	_makefile_print_escape(makefile, target);
	_makefile_print(makefile, "%s\n", "$(EXEEXT)");
}

static int _install_target_library(Makefile * makefile, String const * target)
{
	String const * path;
	String const * p;
	String * soname;
	HostOS os;

	if((path = _makefile_get_config(makefile, target, "install")) == NULL)
		return 0;
	_makefile_mkdir(makefile, path);
	if(configure_can_library_static(makefile->configure))
	{
		/* install the static library */
		_makefile_print(makefile, "%s",
				"\t$(INSTALL) -m 0644 $(OBJDIR)");
		_makefile_print_escape(makefile, target);
		_makefile_print(makefile, ".a $(DESTDIR)");
		_makefile_print_escape(makefile, path);
		_makefile_print(makefile, "/");
		_makefile_print_escape(makefile, target);
		_makefile_print(makefile, ".a\n");
	}
	os = configure_get_os(makefile->configure);
	if((p = _makefile_get_config(makefile, target, "soname")) != NULL)
		soname = string_new(p);
	else if(os == HO_DARWIN)
		/* versioning is different on Darwin/macOS */
		soname = string_new_append(target, ".0.0$(SOEXT)", NULL);
	else if(os == HO_WIN32)
		/* and on Windows */
		soname = string_new_append(target, "$(SOEXT)", NULL);
	else
		soname = string_new_append(target, "$(SOEXT)", ".0", NULL);
	if(soname == NULL)
		return 1;
	/* install the shared library */
	if(os == HO_DARWIN)
	{
		_makefile_print(makefile, "%s",
				"\t$(INSTALL) -m 0755 $(OBJDIR)");
		_makefile_print_escape(makefile, soname);
		_makefile_print(makefile, "%s", " $(DESTDIR)");
		_makefile_print_escape(makefile, path);
		_makefile_print(makefile, "/");
		_makefile_print_escape(makefile, soname);
		_makefile_print(makefile, "\n");
		_makefile_print(makefile, "%s", "\t$(LN) -s -- ");
		_makefile_print_escape(makefile, soname);
		_makefile_print(makefile, " $(DESTDIR)");
		_makefile_print_escape(makefile, path);
		_makefile_print(makefile, "/");
		_makefile_print_escape(makefile, target);
		_makefile_print(makefile, ".0$(SOEXT)\n");
		_makefile_print(makefile, "%s", "\t$(LN) -s -- ");
		_makefile_print_escape(makefile, soname);
		_makefile_print(makefile, "%s", " $(DESTDIR)");
		_makefile_print_escape(makefile, path);
		_makefile_print(makefile, "/");
		_makefile_print_escape(makefile, target);
		_makefile_print(makefile, "$(SOEXT)\n");
	}
	else if(os == HO_WIN32)
	{
		_makefile_print(makefile, "%s",
				"\t$(INSTALL) -m 0755 $(OBJDIR)");
		_makefile_print_escape(makefile, soname);
		_makefile_print(makefile, "%s", " $(DESTDIR)");
		_makefile_print_escape(makefile, path);
		_makefile_print(makefile, "/");
		_makefile_print_escape(makefile, soname);
		_makefile_print(makefile, "\n");
	}
	else
	{
		_makefile_print(makefile, "%s",
				"\t$(INSTALL) -m 0755 $(OBJDIR)");
		_makefile_print_escape(makefile, soname);
		_makefile_print(makefile, ".0 $(DESTDIR)");
		_makefile_print_escape(makefile, path);
		_makefile_print(makefile, "/");
		_makefile_print_escape(makefile, soname);
		_makefile_print(makefile, ".0\n");
		_makefile_print(makefile, "%s", "\t$(LN) -s -- ");
		_makefile_print_escape(makefile, soname);
		_makefile_print(makefile, ".0 $(DESTDIR)");
		_makefile_print_escape(makefile, path);
		_makefile_print(makefile, "/");
		_makefile_print_escape(makefile, soname);
		_makefile_print(makefile, "\n");
		_makefile_print(makefile, "%s", "\t$(LN) -s -- ");
		_makefile_print_escape(makefile, soname);
		_makefile_print(makefile, ".0 $(DESTDIR)");
		_makefile_print_escape(makefile, path);
		_makefile_print(makefile, "/");
		_makefile_print_escape(makefile, target);
		_makefile_print(makefile, "%s", "$(SOEXT)\n");
	}
	string_delete(soname);
	return 0;
}

static void _install_target_libtool(Makefile * makefile, String const * target)
{
	String const * path;

	if((path = _makefile_get_config(makefile, target, "install")) == NULL)
		return;
	_makefile_mkdir(makefile, path);
	_makefile_print(makefile, "%s", "\t$(LIBTOOL) --mode=install $(INSTALL)"
			" -m 0755 $(OBJDIR)");
	_makefile_print_escape(makefile, target);
	_makefile_print(makefile, ".la $(DESTDIR)");
	_makefile_print_escape(makefile, path);
	_makefile_print(makefile, "/");
	_makefile_print_escape(makefile, target);
	_makefile_print(makefile, ".la\n");
	_makefile_print(makefile, "%s", "\t$(LIBTOOL) --mode=finish $(DESTDIR)");
	_makefile_print_escape(makefile, path);
	_makefile_print(makefile, "\n");
}

static void _install_target_object(Makefile * makefile, String const * target)
{
	String const * path;

	if((path = _makefile_get_config(makefile, target, "install")) == NULL)
		return;
	_makefile_mkdir(makefile, path);
	_makefile_print(makefile, "%s%s%s%s/%s\n",
			"\t$(INSTALL) -m 0644 $(OBJDIR)",
			target, " $(DESTDIR)", path, target);
}

static void _install_target_plugin(Makefile * makefile, String const * target)
{
	String const * path;
	String const * mode;
	mode_t m = 0755;
	String * p;

	if((path = _makefile_get_config(makefile, target, "install")) == NULL)
		return;
	if((mode = _makefile_get_config(makefile, target, "mode")) == NULL
			/* XXX these tests are not sufficient */
			|| mode[0] == '\0'
			|| (m = strtol(mode, &p, 8)) == 0
			|| *p != '\0')
		mode = "0755";
	_makefile_mkdir(makefile, path);
	_makefile_print(makefile, "%s%04o%s", "\t$(INSTALL) -m ", m,
			" $(OBJDIR)");
	_makefile_print_escape(makefile, target);
	_makefile_print(makefile, "%s", "$(SOEXT) $(DESTDIR)");
	_makefile_print_escape(makefile, path);
	_makefile_print(makefile, "/");
	_makefile_print_escape(makefile, target);
	_makefile_print(makefile, "%s", "$(SOEXT)\n");
}

static void _install_target_script(Makefile * makefile, String const * target)
{
	String const * path;
	String const * script;
	String const * flags;
	int phony;

	if((path = _makefile_get_config(makefile, target, "install")) == NULL)
		return;
	if((script = _makefile_get_config(makefile, target, "script")) == NULL)
		return;
	flags = _makefile_get_config(makefile, target, "flags");
	phony = _makefile_is_phony(makefile, target);
	_makefile_print(makefile, "\t");
	_makefile_print_escape(makefile, script);
	_makefile_print(makefile, "%s%s%s", " -P \"$(DESTDIR)",
			(path[0] != '\0') ? path : "$(PREFIX)", "\" -i");
	if(flags != NULL && flags[0] != '\0')
		_makefile_print(makefile, " %s", flags);
	_makefile_print(makefile, "%s%s%s%s", " -- \"",
			phony ? "" : "$(OBJDIR)", target, "\"\n");
}

static int _install_include(Makefile * makefile, String const * include);
static int _install_includes(Makefile * makefile)
{
	int ret = 0;
	String const * p;
	String * includes;
	String * q;
	size_t i;
	char c;

	if((p = _makefile_get_config_mode(makefile, NULL, "includes")) == NULL)
		return 0;
	if((includes = string_new(p)) == NULL)
		return 1;
	q = includes;
	for(i = 0; ret == 0; i++)
	{
		if(includes[i] != ',' && includes[i] != '\0')
			continue;
		c = includes[i];
		includes[i] = '\0';
		ret |= _install_include(makefile, includes);
		if(c == '\0')
			break;
		includes += i + 1;
		i = 0;
	}
	string_delete(q);
	return ret;
}

static int _install_include(Makefile * makefile, String const * include)
{
	char const * install;
	ssize_t i;
	String * p = NULL;
	String * directory;

	if((install = _makefile_get_config(makefile, include, "install"))
			== NULL)
	{
		install = "$(INCLUDEDIR)";
		if((i = string_rindex(include, "/")) >= 0)
			if((p = string_new_length(include, i)) == NULL)
				return 2;
	}
	if(p != NULL)
	{
		directory = string_new_append(install, "/", p, NULL);
		string_delete(p);
	}
	else
		directory = string_new(install);
	if(directory == NULL)
		return 2;
	_makefile_mkdir(makefile, directory);
	string_delete(directory);
	_makefile_print(makefile, "%s", "\t$(INSTALL) -m 0644 ");
	_makefile_print_escape(makefile, include);
	_makefile_print(makefile, "%s", " $(DESTDIR)");
	_makefile_print_escape(makefile, install);
	_makefile_print(makefile, "/");
	_makefile_print_escape(makefile, include);
	_makefile_print(makefile, "\n");
	return 0;
}

static int _dist_check(Makefile * makefile, char const * target,
		char const * mode);
static int _dist_install(Makefile * makefile, char const * directory,
		char const * mode, char const * filename);
static int _install_dist(Makefile * makefile)
{
	int ret = 0;
	String const * p;
	String * dist;
	String * q;
	size_t i;
	char c;
	String const * d;
	String const * mode;

	if((p = _makefile_get_config_mode(makefile, NULL, "dist")) == NULL)
		return 0;
	if((dist = string_new(p)) == NULL)
		return 1;
	q = dist;
	for(i = 0;; i++)
	{
		if(dist[i] != ',' && dist[i] != '\0')
			continue;
		c = dist[i];
		dist[i] = '\0';
		if((mode = _makefile_get_config(makefile, dist, "mode"))
				== NULL)
			mode = "0644";
		ret |= _dist_check(makefile, dist, mode);
		if((d = _makefile_get_config(makefile, dist, "install"))
				!= NULL)
			_dist_install(makefile, d, mode, dist);
		if(c == '\0')
			break;
		dist += i + 1;
		i = 0;
	}
	string_delete(q);
	return ret;
}

static int _dist_check(Makefile * makefile, char const * target,
		char const * mode)
{
	char * p;
	mode_t m;

	m = strtol(mode, &p, 8);
	if(mode[0] == '\0' || *p != '\0')
		return error_set_print(PROGNAME_CONFIGURE, 1, "%s: %s%s%s",
				target, "Invalid permissions \"", mode, "\"");
	if(_makefile_is_flag_set(makefile, PREFS_S) && (m & S_ISUID))
		error_set_print(PROGNAME_CONFIGURE, 0, "%s: %s", target,
				"Installed as a SUID file");
	if(_makefile_is_flag_set(makefile, PREFS_S) && (m & S_ISUID))
		error_set_print(PROGNAME_CONFIGURE, 0, "%s: %s", target,
				"Installed as a SGID file");
	if(_makefile_is_flag_set(makefile, PREFS_S)
			&& (m & (S_IXUSR | S_IXGRP | S_IXOTH)))
		error_set_print(PROGNAME_CONFIGURE, 0, "%s: %s", target,
				"Installed as an executable file");
	if(_makefile_is_flag_set(makefile, PREFS_S) && (m & S_IWGRP))
		error_set_print(PROGNAME_CONFIGURE, 0, "%s: %s", target,
				"Installed as a group-writable file");
	if(_makefile_is_flag_set(makefile, PREFS_S) && (m & S_IWOTH))
		error_set_print(PROGNAME_CONFIGURE, 0, "%s: %s", target,
				"Installed as a writable file");
	return 0;
}

static int _dist_install(Makefile * makefile, char const * directory,
		char const * mode, char const * filename)
{
	String * p;
	char const * q;

	if(strchr(filename, '/') != NULL)
	{
		if((p = string_new(filename)) == NULL)
			return -1;
		q = dirname(p);
		/* TODO keep track of the directories created */
		_makefile_print(makefile, "%s%s/%s\n", "\t$(MKDIR) $(DESTDIR)",
				directory, q);
		string_delete(p);
	}
	else
		_makefile_mkdir(makefile, directory);
	_makefile_print(makefile, "%s", "\t$(INSTALL) -m ");
	_makefile_print_escape(makefile, mode);
	_makefile_print(makefile, " ");
	_makefile_print_escape(makefile, filename);
	_makefile_print(makefile, " $(DESTDIR)");
	_makefile_print_escape(makefile, directory);
	_makefile_print(makefile, "/");
	_makefile_print_escape(makefile, filename);
	_makefile_print(makefile, "\n");
	return 0;
}

static int _write_phony_targets(Makefile * makefile);
static int _write_phony(Makefile * makefile, char const ** targets)
{
	size_t i;

	_makefile_print(makefile, "%s:", "\n.PHONY");
	for(i = 0; targets[i] != NULL; i++)
		_makefile_print(makefile, " %s", targets[i]);
	if(_write_phony_targets(makefile) != 0)
		return 1;
	_makefile_print(makefile, "\n");
	return 0;
}

static int _write_phony_targets(Makefile * makefile)
{
	String const * p;
	String * prints;
	size_t i;
	char c;
	String const * type;

	if((p = _makefile_get_config_mode(makefile, NULL, "targets")) == NULL)
		return 0;
	if((prints = string_new(p)) == NULL)
		return 1;
	for(i = 0;; i++)
	{
		if(prints[i] != ',' && prints[i] != '\0')
			continue;
		c = prints[i];
		prints[i] = '\0';
		if((type = _makefile_get_config(makefile, prints, "type"))
				!= NULL)
			switch(enum_string(TT_LAST, sTargetType, type))
			{
				case TT_COMMAND:
				case TT_SCRIPT:
					if(_makefile_is_phony(makefile, prints))
					{
						_makefile_print(makefile, " ");
						_makefile_print_escape(makefile,
								prints);
					}
					break;
			}
		if(c == '\0')
			break;
		prints += i + 1;
		i = 0;
	}
	return 0;
}

static int _uninstall_target(Makefile * makefile, String const * target);
static int _uninstall_include(Makefile * makefile, String const * include);
static int _uninstall_dist(Makefile * makefile, String const * dist);
static int _write_uninstall(Makefile * makefile)
{
	int ret = 0;
	String const * p;
	String * targets;
	String * q;
	String * includes;
	String * dist;
	size_t i;
	char c;

	_makefile_target(makefile, "uninstall", NULL);
	if(_makefile_get_config_mode(makefile, NULL, "subdirs") != NULL)
		_makefile_subdirs(makefile, "uninstall");
	if((p = _makefile_get_config_mode(makefile, NULL, "targets")) != NULL)
	{
		if((targets = string_new(p)) == NULL)
			return 1;
		q = targets;
		for(i = 0; ret == 0; i++)
		{
			if(targets[i] != ',' && targets[i] != '\0')
				continue;
			c = targets[i];
			targets[i] = '\0';
			if(_makefile_is_enabled(makefile, targets) != 0)
				ret = _uninstall_target(makefile, targets);
			if(c == '\0')
				break;
			targets += i + 1;
			i = 0;
		}
		string_delete(q);
	}
	if((p = _makefile_get_config_mode(makefile, NULL, "includes")) != NULL)
	{
		if((includes = string_new(p)) == NULL)
			return 1;
		q = includes;
		for(i = 0; ret == 0; i++)
		{
			if(includes[i] != ',' && includes[i] != '\0')
				continue;
			c = includes[i];
			includes[i] = '\0';
			ret = _uninstall_include(makefile, includes);
			if(c == '\0')
				break;
			includes += i + 1;
			i = 0;
		}
		string_delete(q);
	}
	if((p = _makefile_get_config_mode(makefile, NULL, "dist")) != NULL)
	{
		if((dist = string_new(p)) == NULL)
			return 1;
		q = dist;
		for(i = 0; ret == 0; i++)
		{
			if(dist[i] != ',' && dist[i] != '\0')
				continue;
			c = dist[i];
			dist[i] = '\0';
			ret = _uninstall_dist(makefile, dist);
			if(c == '\0')
				break;
			dist += i + 1;
			i = 0;
		}
		string_delete(q);
	}
	return ret;
}

static int _uninstall_target_library(Makefile * makefile, String const * target,
		String const * path);
static void _uninstall_target_script(Makefile * makefile, String const * target,
		String const * path);
static int _uninstall_target(Makefile * makefile, String const * target)
{
	String const * type;
	String const * path;
	TargetType tt;
	const String rm_destdir[] = "$(RM) -- $(DESTDIR)";

	if((type = _makefile_get_config(makefile, target, "type")) == NULL)
		return 1;
	if((path = _makefile_get_config(makefile, target, "install")) == NULL)
		return 0;
	tt = enum_string(TT_LAST, sTargetType, type);
	switch(tt)
	{
		case TT_BINARY:
			_makefile_print(makefile, "\t%s", rm_destdir);
			_makefile_print_escape(makefile, path);
			_makefile_print(makefile, "/");
			_makefile_print_escape(makefile, target);
			_makefile_print(makefile, "$(EXEEXT)\n");
			break;
		case TT_COMMAND:
			_makefile_print(makefile, "\t%s", rm_destdir);
			_makefile_print_escape(makefile, path);
			_makefile_print(makefile, "/");
			_makefile_print_escape(makefile, target);
			_makefile_print(makefile, "\n");
			break;
		case TT_LIBRARY:
			if(_uninstall_target_library(makefile, target,
						path) != 0)
				return 1;
			break;
		case TT_LIBTOOL:
			_makefile_print(makefile, "\t%s%s", "$(LIBTOOL)"
					" --mode=uninstall ", rm_destdir);
			_makefile_print_escape(makefile, path);
			_makefile_print(makefile, "/");
			_makefile_print_escape(makefile, target);
			_makefile_print(makefile, ".la\n");
			break;
		case TT_OBJECT:
			_makefile_print(makefile, "\t%s", rm_destdir);
			_makefile_print_escape(makefile, path);
			_makefile_print(makefile, "/");
			_makefile_print_escape(makefile, target);
			_makefile_print(makefile, "\n");
			break;
		case TT_PLUGIN:
			_makefile_print(makefile, "\t%s", rm_destdir);
			_makefile_print_escape(makefile, path);
			_makefile_print(makefile, "/");
			_makefile_print_escape(makefile, target);
			_makefile_print(makefile, "$(SOEXT)\n");
			break;
		case TT_SCRIPT:
			_uninstall_target_script(makefile, target, path);
			break;
		case TT_UNKNOWN:
			break;
	}
	return 0;
}

static int _uninstall_target_library(Makefile * makefile, String const * target,
		String const * path)
{
	String * soname;
	String const * p;
	const String rm_destdir[] = "\t$(RM) -- $(DESTDIR)";
	HostOS os;

	if(configure_can_library_static(makefile->configure))
	{
		/* uninstall the static library */
		_makefile_print(makefile, "%s", rm_destdir);
		_makefile_print_escape(makefile, path);
		_makefile_print(makefile, "/");
		_makefile_print_escape(makefile, target);
		_makefile_print(makefile, "%s", ".a\n");
	}
	os = configure_get_os(makefile->configure);
	if((p = _makefile_get_config(makefile, target, "soname")) != NULL)
		soname = string_new(p);
	else if(os == HO_DARWIN)
		/* versioning is different on Darwin/macOS */
		soname = string_new_append(target, ".0.0$(SOEXT)", NULL);
	else if(os == HO_WIN32)
		/* and on Windows */
		soname = string_new_append(target, "$(SOEXT)", NULL);
	else
		soname = string_new_append(target, "$(SOEXT).0", NULL);
	if(soname == NULL)
		return 1;
	/* uninstall the shared library */
	if(os == HO_DARWIN)
	{
		_makefile_print(makefile, "%s", rm_destdir);
		_makefile_print_escape(makefile, path);
		_makefile_print(makefile, "/");
		_makefile_print_escape(makefile, soname);
		_makefile_print(makefile, "\n");
		_makefile_print(makefile, "%s", rm_destdir);
		_makefile_print_escape(makefile, path);
		_makefile_print(makefile, "/");
		_makefile_print_escape(makefile, target);
		_makefile_print(makefile, ".0$(SOEXT)\n");
	}
	else if(os != HO_WIN32)
	{
		_makefile_print(makefile, "%s", rm_destdir);
		_makefile_print_escape(makefile, path);
		_makefile_print(makefile, "/");
		_makefile_print_escape(makefile, soname);
		_makefile_print(makefile, "%s", ".0\n");
		_makefile_print(makefile, "%s", rm_destdir);
		_makefile_print_escape(makefile, path);
		_makefile_print(makefile, "/");
		_makefile_print_escape(makefile, soname);
		_makefile_print(makefile, "\n");
	}
	_makefile_print(makefile, "%s", rm_destdir);
	_makefile_print_escape(makefile, path);
	_makefile_print(makefile, "/");
	_makefile_print_escape(makefile, target);
	_makefile_print(makefile, "$(SOEXT)\n");
	string_delete(soname);
	return 0;
}

static void _uninstall_target_script(Makefile * makefile, String const * target,
		String const * path)
{
	String const * script;
	String const * flags;

	if((script = _makefile_get_config(makefile, target, "script")) == NULL)
		return;
	flags = _makefile_get_config(makefile, target, "flags");
	_makefile_print(makefile, "\t");
	_makefile_print_escape(makefile, script);
	_makefile_print(makefile, "%s%s%s", " -P \"$(DESTDIR)",
			(path[0] != '\0') ? path : "$(PREFIX)", "\" -u");
	if(flags != NULL && flags[0] != '\0')
		_makefile_print(makefile, " %s", flags);
	_makefile_print(makefile, "%s%s%s", " -- \"", target, "\"\n");
}

static int _uninstall_include(Makefile * makefile, String const * include)
{
	String const * install;

	if((install = _makefile_get_config(makefile, include, "install"))
			== NULL)
		install = "$(INCLUDEDIR)";
	_makefile_print(makefile, "%s", "\t$(RM) -- $(DESTDIR)");
	_makefile_print_escape(makefile, install);
	_makefile_print(makefile, "/");
	_makefile_print_escape(makefile, include);
	_makefile_print(makefile, "\n");
	return 0;
}

static int _uninstall_dist(Makefile * makefile, String const * dist)
{
	String const * install;

	if((install = _makefile_get_config(makefile, dist, "install")) == NULL)
		return 0;
	_makefile_print(makefile, "%s", "\t$(RM) -- $(DESTDIR)");
	_makefile_print_escape(makefile, install);
	_makefile_print(makefile, "/");
	_makefile_print_escape(makefile, dist);
	_makefile_print(makefile, "\n");
	return 0;
}


/* accessors */
/* makefile_get_config */
static String const * _makefile_get_config(Makefile * makefile,
		String const * section, String const * variable)
{
	return configure_get_config(makefile->configure, section, variable);
}


/* makefile_get_config_mode */
static String const * _makefile_get_config_mode(Makefile * makefile,
		String const * mode, String const * variable)
{
	return configure_get_config_mode(makefile->configure, mode, variable);
}


/* makefile_get_type */
static TargetType _makefile_get_type(Makefile * makefile,
		String const * target)
{
	String const * type;

	if((type = _makefile_get_config(makefile, target, "type")) == NULL)
		return TT_UNKNOWN;
	return enum_string(TT_LAST, sTargetType, type);
}


/* makefile_is_enabled */
static int _makefile_is_enabled(Makefile * makefile, char const * target)
{
	String const * p;

	if((p = _makefile_get_config(makefile, target, "enabled")) != NULL
			&& strtol(p, NULL, 10) == 0)
		return 0;
	return 1;
}


/* makefile_is_flag_set */
static unsigned int _makefile_is_flag_set(Makefile * makefile,
		unsigned int flag)
{
	return configure_is_flag_set(makefile->configure, flag);
}


/* makefile_is_phony */
static int _makefile_is_phony(Makefile * makefile, char const * target)
{
	String const * p;

	if((p = _makefile_get_config(makefile, target, "phony")) != NULL
			&& strtol(p, NULL, 10) == 1)
		return 1;
	return 0;
}


/* useful */
/* makefile_expand */
static int _makefile_expand(Makefile * makefile, char const * field)
{
	int res = 0;
	char c;

	if(field == NULL)
		return -1;
	if(field[0] != '\0')
		res = _makefile_print(makefile, " ");
	while((c = *(field++)) != '\0')
	{
		if(c == ' ' || c == '\t')
			res |= _makefile_print(makefile, "\\");
		else if(c == ',')
			c = ' ';
		res |= _makefile_print(makefile, "%c", c);
	}
	return (res >= 0) ? 0 : -1;
}


/* makefile_link */
static int _makefile_link(Makefile * makefile, int symlink, char const * link,
		char const * path)
{
	_makefile_print(makefile, "\t$(LN)%s -- %s %s\n", symlink ? " -s" : "",
			link, path);
	return 0;
}


/* makefile_output_extension */
static int _makefile_output_extension(Makefile * makefile,
		String const * extension)
{
	int ret;
	String const * value;
	String * upper;

	if((upper = string_new_append(extension, "EXT", NULL)) == NULL)
		return -1;
	string_toupper(upper);
	value = configure_get_extension(makefile->configure, extension);
	ret = _makefile_output_variable(makefile, upper, value);
	string_delete(upper);
	return ret;
}


/* makefile_output_path */
static int _makefile_output_path(Makefile * makefile, String const * path)
{
	int ret;
	String const * value;
	String * upper;
	String * p;

	if((upper = string_new(path)) == NULL)
		return -1;
	string_toupper(upper);
	value = configure_get_path(makefile->configure, path);
	if(value != NULL && value[0] != '\0')
	{
		if(value[0] != '/')
		{
			p = string_new_append("$(PREFIX)/", value, NULL);
			ret = _makefile_output_variable(makefile, upper, p);
			string_delete(p);
		}
		else
			ret = _makefile_output_variable(makefile, upper, value);
	}
	else
		ret = _makefile_output_variable(makefile, upper, value);
	string_delete(upper);
	return ret;
}


/* makefile_output_program */
static int _makefile_output_program(Makefile * makefile, String const * name,
		unsigned int override)
{
	int ret;
	String const * value;
	String * upper;

	if((upper = string_new(name)) == NULL)
		return -1;
	string_toupper(upper);
	if(override)
		value = _makefile_get_config_mode(makefile, NULL, name);
	else
		value = NULL;
	if(value == NULL)
		value = configure_get_program(makefile->configure, name);
	ret = _makefile_output_variable(makefile, upper, value);
	string_delete(upper);
	return ret;
}


/* makefile_output_variable */
static int _makefile_output_variable(Makefile * makefile, String const * name,
		String const * value)
{
	int res;
	size_t len;

	if(makefile->fp == NULL)
		return 0;
	if(name == NULL || (len = strlen(name)) == 0)
		return -1;
	if(value == NULL)
		value = "";
	res = _makefile_print(makefile, "%s%s%s%s\n", name,
			(len >= 8) ? "" : "\t",
			(strlen(value) > 0) ? "= " : "=", value);
	return (res >= 0) ? 0 : -1;
}


/* makefile_mkdir */
static int _makefile_mkdir(Makefile * makefile, char const * directory)
{
	/* TODO keep track of the directories created */
	if(_makefile_print(makefile, "%s", "\t$(MKDIR) $(DESTDIR)") < 0
			|| _makefile_print_escape(makefile, directory) < 0
			|| _makefile_print(makefile, "\n") < 0)
		return -1;
	return 0;
}


/* makefile_print */
static int _makefile_print(Makefile * makefile, char const * format, ...)
{
	int ret;
	va_list ap;

	va_start(ap, format);
	if(makefile->fp == NULL)
		ret = vsnprintf(NULL, 0, format, ap);
	else
		ret = vfprintf(makefile->fp, format, ap);
	va_end(ap);
	return ret;
}


/* makefile_print_escape */
static int _makefile_print_escape(Makefile * makefile, char const * str)
{
	if(str == NULL)
		return -1;
	if(makefile->fp == NULL)
		return 0;
	while(*str != '\0')
	{
		if(*str == ' ' || *str == '\t')
			if(fputc('\\', makefile->fp) == EOF)
				return -1;
		if(fputc(*(str++), makefile->fp) == EOF)
			return -1;
	}
	return 0;
}


/* makefile_print_escape_variable */
static int _makefile_print_escape_variable(Makefile * makefile,
		char const * str)
{
	char c;

	if(str == NULL)
		return -1;
	if(makefile->fp == NULL)
		return 0;
	while((c = *(str++)) != '\0')
	{
		if(c == ' ' || c == '\t')
			c = '_';
		if(fputc(c, makefile->fp) == EOF)
			return -1;
	}
	return 0;
}


/* makefile_print_target */
static int _print_target_library(Makefile * makefile, String const * target);

static int _makefile_print_target(Makefile * makefile, String const * target)
{
	int ret = 0;
	String const * type;
	int phony;

	if((type = _makefile_get_config(makefile, target, "type")) == NULL)
	{
		_makefile_print(makefile, "%s", target);
		return 0;
	}
	switch(enum_string(TT_LAST, sTargetType, type))
	{
		case TT_BINARY:
			_makefile_print(makefile, "%s", "$(OBJDIR)");
			_makefile_print_escape(makefile, target);
			_makefile_print(makefile, "%s", "$(EXEEXT)");
			break;
		case TT_COMMAND:
			phony = _makefile_is_phony(makefile, target);
			_makefile_print(makefile, "%s",
					phony ? "" : "$(OBJDIR)");
			_makefile_print_escape(makefile, target);
			break;
		case TT_LIBRARY:
			ret |= _print_target_library(makefile, target);
			break;
		case TT_LIBTOOL:
			_makefile_print(makefile, "%s", "$(OBJDIR)");
			_makefile_print_escape(makefile, target);
			_makefile_print(makefile, "%s", ".la");
			break;
		case TT_OBJECT:
		case TT_UNKNOWN:
			_makefile_print(makefile, "$(OBJDIR)");
			_makefile_print_escape(makefile, target);
			break;
		case TT_SCRIPT:
			phony = _makefile_is_phony(makefile, target);
			_makefile_print(makefile, "%s",
					phony ? "" : "$(OBJDIR)");
			_makefile_print_escape(makefile, target);
			break;
		case TT_PLUGIN:
			_makefile_print(makefile, "%s", "$(OBJDIR)");
			_makefile_print_escape(makefile, target);
			_makefile_print(makefile, "%s", "$(SOEXT)");
			break;
	}
	return ret;
}

static int _print_target_library(Makefile * makefile, String const * target)
{
	String * soname;
	String const * p;
	String const * sep = "";
	HostOS os;

	if((p = _makefile_get_config(makefile, target, "soname")) != NULL)
		soname = string_new(p);
	else if(configure_get_os(makefile->configure) == HO_DARWIN)
		/* versioning is different on Darwin/macOS */
		soname = string_new_append(target, ".0.0$(SOEXT)", NULL);
	else if(configure_get_os(makefile->configure) == HO_WIN32)
		/* and on Windows */
		soname = string_new_append(target, "$(SOEXT)", NULL);
	else
		soname = string_new_append(target, "$(SOEXT)", ".0", NULL);
	if(soname == NULL)
		return 1;
	if(configure_can_library_static(makefile->configure))
	{
		/* generate a static library */
		_makefile_print(makefile, "%s", "$(OBJDIR)", target, ".a");
		_makefile_print_escape(makefile, target);
		_makefile_print(makefile, "%s", ".a");
		sep = " ";
	}
	if((os = configure_get_os(makefile->configure)) == HO_DARWIN)
	{
		_makefile_print(makefile, "%s%s", sep, "$(OBJDIR)");
		_makefile_print_escape(makefile, soname);
		_makefile_print(makefile, " $(OBJDIR)");
		_makefile_print_escape(makefile, target);
		_makefile_print(makefile, "%s", ".0$(SOEXT) $(OBJDIR)");
		_makefile_print_escape(makefile, target);
		_makefile_print(makefile, "$(SOEXT)");
	}
	else if(os == HO_WIN32)
	{
		_makefile_print(makefile, "%s%s", sep, "$(OBJDIR)");
		_makefile_print_escape(makefile, soname);
	}
	else
	{
		_makefile_print(makefile, "%s%s", sep, "$(OBJDIR)");
		_makefile_print_escape(makefile, soname);
		_makefile_print(makefile, ".0 $(OBJDIR)");
		_makefile_print_escape(makefile, soname);
		_makefile_print(makefile, " $(OBJDIR)");
		_makefile_print_escape(makefile, target);
		_makefile_print(makefile, "%s", "$(SOEXT)");
	}
	string_delete(soname);
	return 0;
}


/* makefile_remove */
static int _makefile_remove(Makefile * makefile, int recursive, ...)
{
	va_list ap;
	char const * sep = " -- ";
	char const * p;

	_makefile_print(makefile, "\t$(RM)%s", recursive ? " -r" : "");
	va_start(ap, recursive);
	while((p = va_arg(ap, char const * )) != NULL)
	{
		_makefile_print(makefile, "%s%s", sep, p);
		sep = " ";
	}
	va_end(ap);
	_makefile_print(makefile, "\n");
	return 0;
}


/* makefile_subdirs */
static int _makefile_subdirs(Makefile * makefile, char const * target)
{
	if(target != NULL)
		_makefile_print(makefile,
				"\t@for i in $(SUBDIRS); do (cd \"$$i\" && \\\n"
				"\t\tif [ -n \"$(OBJDIR)\" ]; then \\\n"
				"\t\t$(MAKE) OBJDIR=\"$(OBJDIR)$$i/\" %s; \\\n"
				"\t\telse $(MAKE) %s; fi) || exit; done\n",
				target, target);
	else
		_makefile_print(makefile, "%s",
				"\t@for i in $(SUBDIRS); do (cd \"$$i\" && \\\n"
				"\t\tif [ -n \"$(OBJDIR)\" ]; then \\\n"
				"\t\t([ -d \"$(OBJDIR)$$i\" ]"
				" || $(MKDIR) -- \"$(OBJDIR)$$i\") && \\\n"
				"\t\t$(MAKE) OBJDIR=\"$(OBJDIR)$$i/\"; \\\n"
				"\t\telse $(MAKE); fi) || exit; done\n");
	return 0;
}


/* makefile_target */
static int _makefile_target(Makefile * makefile, char const * target, ...)
{
	va_list ap;
	char const * sep = " ";
	char const * p;

	if(target == NULL)
		return -1;
	_makefile_print(makefile, "\n%s:", target);
	va_start(ap, target);
	while((p = va_arg(ap, char const *)) != NULL)
		_makefile_print(makefile, "%s%s", sep, p);
	va_end(ap);
	_makefile_print(makefile, "\n");
	return 0;
}


#ifdef WITH_UNUSED
/* makefile_targetv */
static int _makefile_targetv(FILE * fp, char const * target,
		char const ** depends)
{
	char const ** p;

	if(target == NULL)
		return -1;
	_makefile_print(makefile, "\n%s:", target);
	if(depends != NULL)
		for(p = depends; *p != NULL; p++)
			_makefile_print(makefile, " %s", *p);
	_makefile_print(makefile, "\n");
	return 0;
}
#endif
