tconf.c   [plain text]


/*
 * Copyright (c) 2007 Apple Inc. All rights reserved.
 *
 * @APPLE_LICENSE_HEADER_START@
 * 
 * This file contains Original Code and/or Modifications of Original Code
 * as defined in and that are subject to the Apple Public Source License
 * Version 2.0 (the 'License'). You may not use this file except in
 * compliance with the License. Please obtain a copy of the License at
 * http://www.opensource.apple.com/apsl/ and read it before using this
 * file.
 * 
 * The Original Code and all software distributed under the License are
 * distributed on an 'AS IS' basis, WITHOUT WARRANTY OF ANY KIND, EITHER
 * EXPRESS OR IMPLIED, AND APPLE HEREBY DISCLAIMS ALL SUCH WARRANTIES,
 * INCLUDING WITHOUT LIMITATION, ANY WARRANTIES OF MERCHANTABILITY,
 * FITNESS FOR A PARTICULAR PURPOSE, QUIET ENJOYMENT OR NON-INFRINGEMENT.
 * Please see the License for the specific language governing rights and
 * limitations under the License.
 * 
 * @APPLE_LICENSE_HEADER_END@
 */
// Kevin Van Vechten <kvv@apple.com>

#include <errno.h>
#include <stdio.h>
#include <unistd.h>
#include <sys/types.h>
#include <sys/stat.h>

#include "utils.h"

#define TARGET_CONFIG_DIR_PATH "/usr/local/share/TargetConfigs"
#define TARGET_CONFIG_DEFAULT "MacOSX"
#define TARGET_CONFIG_PLIST_VERSION 0

void
usage() {
	fprintf(stderr, "usage: tconf \n"
		"\t--product\n"
		"\t--cflags\n"
		"\t--cxxflags\n"
		"\t--ldflags\n"
		"\t--cc\n"
		"\t--cpp\n"
		"\t--cxx\n"
		"\t--ld\n"
		"\t[-q] --test <variable>\n");
	exit(EXIT_FAILURE);
}

char*
find_config() {
	char* result;
	const char* path = getenv("RC_TARGET_CONFIG");
	const char* next_root = getenv("NEXT_ROOT");

	// search path is as follows:
	// 1) $(NEXT_ROOT)/$(RC_TARGET_CONFIG)
	// 2) $(NEXT_ROOT)/usr/local/share/TargetConfigs/$(RC_TARGET_CONFIG).plist
	// 3) $(RC_TARGET_CONFIG)
	// 4) /usr/local/share/TargetConfigs/$(RC_TARGET_CONFIG).plist
	// 5) $(NEXT_ROOT)/usr/local/share/TargetConfigs/$(DEFAULT).plist
	// 6) /usr/local/share/TargetConfigs/$(DEFAULT).plist

	if (path && next_root) {
		// 1)
		asprintf(&result, "%s/%s", next_root, path);
		if (is_file(result)) return result;
		free(result);

		// 2)
		asprintf(&result, "%s/%s/%s.plist", next_root,
			TARGET_CONFIG_DIR_PATH, path);
		if (is_file(result)) return result;
		free(result);
	}
	if (path) {
		// 3)
		if (is_file(path)) return strdup(path);
		
		// 4)
		asprintf(&result, "%s/%s.plist",
			TARGET_CONFIG_DIR_PATH, path);
		if (is_file(result)) return result;
		free(result);
	}
	if (next_root) {
		// 5)
		asprintf(&result, "%s/%s/%s.plist", next_root,
			TARGET_CONFIG_DIR_PATH, TARGET_CONFIG_DEFAULT);
		if (is_file(result)) return result;
		free(result);
	}
	// 6)
	asprintf(&result, "%s/%s.plist",
		TARGET_CONFIG_DIR_PATH, TARGET_CONFIG_DEFAULT);
	if (is_file(result)) return result;
	free(result);

	return NULL;
}

CFDictionaryRef
read_config() {
	char* path = find_config();
	if (!path) {
		fprintf(stderr, "tconf: no target configuration found\n");
		exit(EXIT_FAILURE);
	}
	
	CFPropertyListRef plist = read_plist(path);
	if (!plist || CFGetTypeID(plist) != CFDictionaryGetTypeID()) {
		fprintf(stderr,
			"tconf: invalid target configuration: %s\n", path);
		exit(EXIT_FAILURE);
	}
	CFNumberRef version = CFDictionaryGetValue(plist,
				CFSTR("TargetConfigVersion"));
	int v = -1;
	if (!version || CFGetTypeID(version) != CFNumberGetTypeID() ||
		!CFNumberGetValue(version, kCFNumberIntType, &v) ||
		v > TARGET_CONFIG_PLIST_VERSION) {
		fprintf(stderr,
			"tconf: invalid target configuration version: %d\n", v);
		exit(EXIT_FAILURE);
	}
	free(path);
	return plist;
}

CFTypeRef
lookup_config(CFDictionaryRef config, CFStringRef key) {
	config = CFDictionaryGetValue(config,
			CFSTR("TargetConditionals"));
	return config ? CFDictionaryGetValue(config, key) : NULL;
}

CFTypeRef
lookup_arch_config(CFDictionaryRef config, CFStringRef key) {
	CFTypeRef res = 0;

	CFStringRef env = cfstr(getenv("RC_ARCHS"));
	CFArrayRef archs = tokenizeString(env);
	if (env) CFRelease(env);

	if (!archs) return NULL;

	CFDictionaryRef target_archs = CFDictionaryGetValue(config,
					CFSTR("TargetArchitectures"));
	if (!target_archs) {
		CFRelease(archs);
		return NULL;
	}

	CFIndex i, count = CFArrayGetCount(archs);
	for (i = 0; i < count; ++i) {
		CFStringRef arch = CFArrayGetValueAtIndex(archs, i);
		CFDictionaryRef arch_config = CFDictionaryGetValue(target_archs,
						arch);
		if (arch_config) {
			res = CFDictionaryGetValue(arch_config, key);
		}
		if (res) break;
	}

	CFRelease(archs);
	return res;
}

int
cmd_test(int qflag, CFDictionaryRef config, const char* var) {
	CFTypeRef res = NULL;

	if (strncmp(var, "TARGET_OS_", 10) == 0 || 
		strncmp(var, "TARGET_RT_", 10) == 0 ||
		strncmp(var, "TARGET_CPU_", 11) == 0 ||
		strncmp(var, "TARGET_HAVE_", 12) == 0) {

		CFStringRef key = cfstr(var);
		res = lookup_arch_config(config, key);
		if (!res) res = lookup_config(config, key);
		CFRelease(key);
	} else {
		fprintf(stderr, "tconf: invalid target conditional: %s\n", var);
		return -1;
	}

	if (!res) res = kCFBooleanFalse;

	if (!qflag) {
		CFTypeID type = CFGetTypeID(res);
		if (type == CFBooleanGetTypeID()) {
			if (CFBooleanGetValue(res)) printf("YES\n");
			else printf("NO\n");
		} else if (type == CFStringGetTypeID() ||
			   type == CFNumberGetTypeID()) {
			cfprintf(stdout, "%@\n", res);
		} else {
			cfprintf(stderr, "tconf: invalid type for %s: %@\n",
				var, CFCopyTypeIDDescription(type));
		}
	} else {
		exit(res != kCFBooleanFalse ? EXIT_SUCCESS : EXIT_FAILURE);
	}
	return 0;
}

// Print a config value using the following:
// 1) Print value from environment variable envkey
// 2) Print value from plist TargetConfiguration key
// 3) Print value from dflt
int
cmd_config(CFDictionaryRef config,
	const char* keystr,
	const char* envkey, 
	const char* dflt) {

	// 1)
	if (envkey) {
 		const char* env = getenv(envkey);
		if (env) {
			printf("%s\n", env);
			return 0;
		}
	}

	// 2)
	CFStringRef key = cfstr(keystr);
	CFTypeRef val = NULL;
	if (key) {
		CFDictionaryRef target_config = CFDictionaryGetValue(config,
					CFSTR("TargetConfiguration"));

		if (target_config &&
			CFGetTypeID(target_config) == CFDictionaryGetTypeID()) {
			val = CFDictionaryGetValue(target_config, key);
		}
	}
	if (val) cfprintf(stdout, "%@\n", val);
	// 3)
	else if (dflt) printf("%s\n", dflt);

	CFRelease(key);
	return 0;
}

void
_export_target_cond(CFStringRef key, CFTypeRef value, FILE* f) {
	CFTypeID type = CFGetTypeID(value);
	if (type == CFBooleanGetTypeID()) {
		int i = CFBooleanGetValue(value);
		value = CFNumberCreate(NULL, kCFNumberIntType, &i);
	} else if (type == CFStringGetTypeID()) {
		// XXX escape string
		value = CFStringCreateWithFormat(NULL, NULL,
				CFSTR("\"%@\""), value);
	} else if (type == CFNumberGetTypeID()) {
		CFRetain(value);
	} else {
		return;
	}

	cfprintf(f,
"#ifndef %@\n"
"#define %@ %@\n"
"#endif\n"
"\n",
	key, key, value);
	CFRelease(value);
}

int
export_target_conds(FILE* f, CFDictionaryRef config) {
	CFDictionaryRef target_conds = CFDictionaryGetValue(config,
						CFSTR("TargetConditionals"));
	if (target_conds) {
		CFDictionaryApplyFunction(target_conds,
		    (CFDictionaryApplierFunction)&_export_target_cond,
		    (void*)f);
	}
	return 0;
}

void
_export_target_arch_conds(CFStringRef key, CFTypeRef value, FILE* f) {
	cfprintf(f,
"#if defined(__%@__)\n",
		key);
	CFDictionaryApplyFunction(value,
		(CFDictionaryApplierFunction)&_export_target_cond,
		(void*)f);
	cfprintf(f,
"#endif\n");
}
	


int
export_target_arch_conds(FILE* f, CFDictionaryRef config) {
	CFDictionaryRef target_archs = CFDictionaryGetValue(config,
						CFSTR("TargetArchitectures"));

        if (target_archs) {
                CFDictionaryApplyFunction(target_archs,
		    (CFDictionaryApplierFunction)&_export_target_arch_conds,
		    (void*)f);
	}

	return 0;
}

int
cmd_export_header(CFDictionaryRef config) {
	cfprintf(stdout,
"// TargetConfig.h is auto-generated by tconf(1); Do not edit.\n"
"// Target: %@\n"
"\n"
"#ifndef __TARGET_CONFIG_H__\n"
"#define __TARGET_CONFIG_H__\n"
"\n"
"#include <sys/cdefs.h>\n"
"#include <TargetConditionals.h>\n"
"\n"
"__BEGIN_DECLS\n"
"\n",
	CFDictionaryGetValue(config, CFSTR("TargetConfigProduct")));

#if 0
	export_target_arch_conds(stdout, config);
#endif
	export_target_conds(stdout, config);

	printf(
"__END_DECLS\n"
"\n"
"#endif // __TARGET_CONFIG_H__\n");

	return 0;
}

int
main(int argc, char* argv[]) {

	if (argc < 2) usage();
	
	const char* cmd = argv[1];

	int qflag = 0;

	if (strcmp(cmd, "-q") == 0) {
		qflag = 1;
		--argc;
		++argv;
	}

	CFDictionaryRef config = read_config();
	
	if (strcmp(cmd, "--cflags") == 0) {
		cmd_config(config, "CFLAGS", "RC_CFLAGS", NULL);
	} else if (strcmp(cmd, "--cxxflags") == 0) {
		cmd_config(config, "CXXFLAGS", "RC_CFLAGS", NULL);
	} else if (strcmp(cmd, "--ldflags") == 0) {
		cmd_config(config, "LDFLAGS", NULL, NULL);
	} else if (strcmp(cmd, "--cppflags") == 0) {
		cmd_config(config, "CPPFLAGS", NULL, NULL);
	} else if (strcmp(cmd, "--cc") == 0) {
		cmd_config(config, "CC", NULL, "/usr/bin/cc");
	} else if (strcmp(cmd, "--cpp") == 0) {
		cmd_config(config, "CPP", NULL, "/usr/bin/ccp");
	} else if (strcmp(cmd, "--cxx") == 0) {
		cmd_config(config, "CXX", NULL, "/usr/bin/c++");
	} else if (strcmp(cmd, "--ld") == 0) {
		cmd_config(config, "LD", NULL, "/usr/bin/ld");
	} else if (strcmp(cmd, "--includes") == 0) {
	} else if (strcmp(cmd, "--export-header") == 0) {
		cmd_export_header(config);
	} else if (strcmp(cmd, "--product") == 0) {
		CFStringRef product = CFDictionaryGetValue(config,
			CFSTR("TargetConfigProduct"));
		if (product) cfprintf(stdout, "%@\n", product);
	} else if (strcmp(cmd, "--test") == 0) {
		if (argc < 3) usage();
		cmd_test(qflag, config, argv[2]);
	}
	
	return EXIT_SUCCESS;
}