Commits

Peter Hosey  committed 1387089

Created a new FileType class and moved a bunch of logic there.

  • Participants
  • Parent commits 5ad53df

Comments (0)

Files changed (1)

File incarnate.py

 OPT__AFFECTS_OPTION = '_AFFECTS_OPTION'
 OPT__USAGE_FLAG = '_USAGE_FLAG'
 OPT__STRIP_OUTPUT_TYPE_FOR_USAGE = '_STRIP_OUTPUT_TYPE_FOR_USAGE'
-OPT__PROCESSED_FILES = '_PROCESSED_FILES'
 OPT__DEFAULT_PCH = '_DEFAULT_PCH'
+OPT__ALL_INPUT_FILES_AT_ONCE = '_ALL_INPUT_FILES_AT_ONCE'
 
 default_CC = os.environ.get(OPT_CC, 'xcrun clang')
 default_CC = default_CC.split(' ')
 		OPT__AFFECTS_OPTION: OPT_CFLAGS,
 		OPT__USAGE_FLAG: '-include',
 		OPT__STRIP_OUTPUT_TYPE_FOR_USAGE: True,
-		OPT__PROCESSED_FILES: [], #The path to every precompiled pch is stored here.
 	},
 	'o': {
 		OPT_CC: default_CC,
 		OPT_LDFLAGS: ['-framework', 'Cocoa'],
 		OPT__OUTPUT_TYPE: '',
 		OPT__OUTPUT_DIR: products_dir,
+		OPT__ALL_INPUT_FILES_AT_ONCE: True,
 	},
 }
 
-input_dirs = ['.']
-files_by_type = {
-	'm': list(),
-	'c': list(),
-	'pch': list(),
-	'o': [os.path.join(temps_dir, name) for name in os.listdir(temps_dir) if name.endswith('.o')],
-}
-file_type_order = ['pch', 'm', 'c', 'o']
-
-for src_dir in input_dirs:
-	files = os.listdir(src_dir)
-	for filename in files:
-		root, ext = os.path.splitext(filename)
-		ext = ext.lstrip('.')
-		if ext in files_by_type:
-			path = os.path.join(src_dir, filename)
-			files_by_type[ext].append(path)
-			del path
-		del root, ext
-	del files
-
-if len(files_by_type['pch']) == 0:
-	default_pch_contents = """\
-#include <stdbool.h>
-#include <sys/types.h>
-"""
-	default_pch_contents_c = default_pch_contents + """\
-#include <stdlib.h>
-#include <stdio.h>
-#include <fcntl.h>
-#include <unistd.h>
-#include <dispatch/dispatch.h>
-#include <CoreFoundation/CoreFoundation.h>
-"""
-	default_pch_contents_objc = default_pch_contents_c + """\
-#import <Cocoa/Cocoa.h>
-"""
-
-	def write_pch(contents, file_type, filename):
-		pch_path = os.path.join(pch_dir, 'Default-' + file_type + '.pch')
-
-		try:
-			pch_stat = os.stat(pch_path)
-		except OSError:
-			build_pch = True
-		else:
-			build_pch = (pch_stat.st_size != len(contents))
-
-		if build_pch:
-			pch_file = open(pch_path, 'w')
-			pch_file.write(contents)
-			pch_file.close()
-			files_by_type['pch'].append(pch_path)
-		else:
-			# We have already built it, so set it as the default PCH for this type.
-			defaults[file_type][OPT__DEFAULT_PCH] = pch_path
-
-	if len(files_by_type['c']):
-		write_pch(default_pch_contents_c, 'c', 'Default.pch')
-	if len(files_by_type['m']):
-		write_pch(default_pch_contents_objc, 'm', 'Default.pch')
-
 import sys
-
 class SubprocessPool(object):
 	def __init__(self):
 		self.processes = {}
 class BuildAction(object):
 	"An action to produce one file from one other file."
 	name = '<Unspecified Build Action>'
-	def __init__(self, in_path, out_path):
+	def __init__(self, in_path, out_path=None):
 		self.input_path = in_path
+
+		ext = os.path.splitext(self.input_path)[-1].lstrip('.')
+		self.type = FileType(ext)
+
+		if out_path is None:
+			src_dir, filename = os.path.split(self.input_path)
+			try:
+				output_dir = self.parameters[OPT__OUTPUT_DIR]
+			except KeyError:
+				output_dir = src_dir
+			try:
+				output_type = self.parameters[OPT__OUTPUT_TYPE]
+			except KeyError:
+				output_filename = filename
+			else:
+				if output_type:
+					output_type = '.' + output_type
+				if self.parameters.get(OPT__OUTPUT_STRIP_INPUT_EXTENSION, True):
+					filename_base, src_ext = os.path.splitext(filename)
+				else:
+					filename_base = filename
+				output_filename = filename_base + output_type
+			out_path = os.path.join(output_dir, output_filename)
 		self.output_path = out_path
 
-		self.parameters = defaults[os.path.splitext(self.input_path)[-1].lstrip('.')]
+		self.process = None
 
-		self.process = None
-	
+	@property
+	def parameters(self):
+		return self.type.parameters
+
 	def fail(self, *errorMessageStrings):
 		"Visibly fail the build. errorMessageStrings should be a series of arguments such as you would pass to print."
 		sys.exit(' '.join([self.name, self.output_path + ':'] + map(str, errorMessageStrings)))
 		"Returns True if any of the files identified by an input path is older than the file identified by the output path. Exit the program if an input file is missing (more precisely, if stat fails on it)."
 		stats = {}
 		for input_path in input_paths:
-			print 'Checking existence of', input_path
 			try:
 				input_stat = os.stat(input_path)
 			except OSError:
 		for input_path in input_paths:
 			input_stat = stats[input_path]
 
-			print 'Checking', input_path, 'vs.', output_path
 			try:
 				output_stat = os.stat(output_path)
 			except OSError:
 	def runIfNeeded(self):
 		needed = self.isNeeded
 		if needed:
+			print self.input_path, '->', self.output_path
 			self.run()
 		return needed
 
 				self._include_flags_from_paths(input_dirs) +
 				self._include_flags_from_paths([temps_dir]) +
 				arch_flags +
-				self.parameters[OPT_CFLAGS] +
-				self.parameters[OPT_LDFLAGS] +
-				[path,
-				 '-o', output_path])
+				self.parameters.get(OPT_CFLAGS, []) +
+				self.parameters.get(OPT_LDFLAGS, []) +
+				[self.input_path,
+				 '-o', self.output_path])
 
 	def run(self):
 		argv = self.argv
 class LinkAction(CompileAction):
 	name = 'Link'
 	def __init__(self, object_paths):
+		if isinstance(object_paths, basestring):
+			object_paths = [object_paths]
 		self.object_paths = object_paths
 		executable_name = os.path.split(os.path.realpath('.'))[-1]
 		self.output_path = os.path.join(products_dir, executable_name)
 
-		self.parameters = defaults['o']
+		self.type = FileType('o')
+
+	def add_file(self, path):
+		self.object_paths.append(path)
 
 	@property
 	def isNeeded(self):
 	def argv(self):
 		return self.compiler + params[OPT_LDFLAGS] + self.object_paths + ['-o', self.output_path]
 
+class FileType(object):
+	registered_types = {}
+	registered_type_order = []
+
+	@classmethod
+	def existing_type(self, filename_extension):
+		filename_extension = filename_extension.lstrip('.')
+		return self.registered_types.get(filename_extension, None)
+
+	@classmethod
+	def register_type(self, filename_extension, type):
+		filename_extension = filename_extension.lstrip('.')
+		self.registered_types[filename_extension] = type
+		self.registered_type_order.append(type)
+
+	@classmethod
+	def order(self):
+		return self.registered_type_order
+
+	def __init__(self, filename_extension, action_class=BuildAction, files=[]):
+		self.extension = filename_extension.lstrip('.')
+
+		self.action_class = action_class
+		self.file_paths = [] #Files are added later in a method call (to add build actions at the same time)
+		self.actions = []
+
+		self.add_files(files)
+
+		self.__class__.register_type(self.extension, self)
+
+	def add_file(self, path):
+		if self.parameters.get(OPT__ALL_INPUT_FILES_AT_ONCE, False):
+			try:
+				action = self.actions[0]
+			except IndexError:
+				action = None
+			else:
+				action.add_file(path)
+		else:
+			action = None
+
+		if action is None:
+			action = self.action_class(path)
+			self.actions.append(action)
+
+		self.file_paths.append(path)
+
+	def add_files(self, paths):
+		for path in paths:
+			self.add_file(path)
+
+	@property
+	def parameters(self):
+		return defaults.get(self.extension, {})
+
+	def run_all_actions(self):
+		pool = SubprocessPool()
+
+		for action in self.actions:
+			action.run()
+			pool.add(action)
+
+		affected_types = self.parameters.get(OPT__AFFECTS_TYPES, None)
+		if affected_types is not None:
+			affected_types = map(FileType, affected_types)
+
+			flag = self.parameters.get(OPT__USAGE_FLAG, None)
+			for affected_type in affected_types:
+				for output_path in (action.output_path for action in self.actions):
+					if output_path.startswith(temps_dir + '/') and not output_path.endswith('-' + affected_type.extension + '.pch.gch'):
+						# This is a default PCH, but the wrong one for this type. Skip it.
+						continue
+					if self.parameters.get(OPT__STRIP_OUTPUT_TYPE_FOR_USAGE, False):
+						suffix = '.' + self.parameters[OPT__OUTPUT_TYPE]
+						if output_path.endswith(suffix):
+							output_path = output_path[:-len(suffix)]
+					affected_type.parameters[self.parameters[OPT__AFFECTS_OPTION]].insert(0, output_path)
+					if flag is not None:
+						affected_type.parameters[self.parameters[OPT__AFFECTS_OPTION]].insert(0, flag)
+
+		return pool.wait_for_all()
+
+def registered_types_in_order(FileType_class=FileType):
+	return FileType_class.registered_type_order
+def FileType(filename_extension, action_class=BuildAction, files=[], FileType_class=FileType):
+	assert filename_extension
+	type = FileType_class.existing_type(filename_extension)
+	if not type:
+		type = FileType_class(filename_extension, action_class, files)
+	assert type.action_class != BuildAction
+	return type
+
+FileType.registered_types_in_order = registered_types_in_order
+del registered_types_in_order
+
+input_dirs = ['.']
+FileType('pch', CompileAction)
+FileType('m', CompileAction)
+FileType('c', CompileAction)
+FileType('o', LinkAction, [os.path.join(temps_dir, name) for name in os.listdir(temps_dir) if name.endswith('.o')])
+
+for src_dir in input_dirs:
+	files = os.listdir(src_dir)
+	for filename in files:
+		root, ext = os.path.splitext(filename)
+		try:
+			type = FileType(ext)
+		except AssertionError:
+			pass
+		else:
+			path = os.path.join(src_dir, filename)
+			type.add_file(path)
+			del path
+		del root, ext
+	del files
+
+if len(FileType('pch').file_paths) == 0:
+	default_pch_contents = """\
+#include <stdbool.h>
+#include <sys/types.h>
+"""
+	default_pch_contents_c = default_pch_contents + """\
+#include <stdlib.h>
+#include <stdio.h>
+#include <fcntl.h>
+#include <unistd.h>
+#include <dispatch/dispatch.h>
+#include <CoreFoundation/CoreFoundation.h>
+"""
+	default_pch_contents_objc = default_pch_contents_c + """\
+#import <Cocoa/Cocoa.h>
+"""
+
+	def write_pch(contents, file_type, filename):
+		pch_path = os.path.join(pch_dir, 'Default-' + file_type + '.pch')
+
+		try:
+			pch_stat = os.stat(pch_path)
+		except OSError:
+			build_pch = True
+		else:
+			build_pch = (pch_stat.st_size != len(contents))
+
+		if build_pch:
+			pch_file = open(pch_path, 'w')
+			pch_file.write(contents)
+			pch_file.close()
+			FileType('pch').add_file(pch_path)
+		else:
+			# We have already built it, so set it as the default PCH for this type.
+			defaults[file_type][OPT__DEFAULT_PCH] = pch_path
+
+	if len(FileType('c').file_paths):
+		write_pch(default_pch_contents_c, 'c', 'Default.pch')
+	if len(FileType('m').file_paths):
+		write_pch(default_pch_contents_objc, 'm', 'Default.pch')
+
 pool = SubprocessPool()
 
-for ext in file_type_order[:-1]:
-	jobs = {}
-
-	params = defaults[ext]
-	params.setdefault(OPT_CFLAGS, [])
-	params.setdefault(OPT_LDFLAGS, [])
-
-	files = files_by_type[ext]
-	output_files = []
-	for path in files:
-		src_dir, filename = os.path.split(path)
-		try:
-			output_dir = params[OPT__OUTPUT_DIR]
-		except KeyError:
-			output_dir = src_dir
-		try:
-			output_type = params[OPT__OUTPUT_TYPE]
-		except KeyError:
-			output_filename = filename
-		else:
-			if output_type:
-				output_type = '.' + output_type
-			if params.get(OPT__OUTPUT_STRIP_INPUT_EXTENSION, True):
-				filename_base, src_ext = os.path.splitext(filename)
-			else:
-				filename_base = filename
-			output_filename = filename_base + output_type
-		output_path = os.path.join(output_dir, output_filename)
-		print path, '->', output_path
-
-		action = CompileAction(path, output_path)
-		if action.runIfNeeded():
-			pool.add(action)
-
-		output_files.append(output_path)
-		
-		output_type = os.path.splitext(output_path)[-1].lstrip('.')
-		if output_type in file_type_order:
-			files_by_type[output_type].append(output_path)
-
-		output_type = os.path.splitext(output_path)[-1].lstrip('.')
-		if output_type in file_type_order:
-			files_by_type[output_type].append(output_path)
-
-	pool.wait_for_all()
-
-	# PCH postprocessing.
-	processed_files = params.get(OPT__PROCESSED_FILES, None)
-	if processed_files is not None:
-		processed_files += output_files
-
-		if params.get(OPT__AFFECTS_TYPES, None) is not None:
-			flag = params.get(OPT__USAGE_FLAG, None)
-			for affected_type in params[OPT__AFFECTS_TYPES]:
-				for output_path in processed_files:
-					if output_path.startswith(temps_dir + '/') and not output_path.endswith('-' + affected_type + '.pch.gch'):
-						# This is a default PCH, but the wrong one for this type. Skip it.
-						continue
-					if params.get(OPT__STRIP_OUTPUT_TYPE_FOR_USAGE, False):
-						suffix = '.' + params[OPT__OUTPUT_TYPE]
-						if output_path.endswith(suffix):
-							output_path = output_path[:-len(suffix)]
-					defaults[affected_type][params[OPT__AFFECTS_OPTION]].insert(0, output_path)
-					if flag is not None:
-						defaults[affected_type][params[OPT__AFFECTS_OPTION]].insert(0, flag)
-
-# Generate the final executable.
-ext = file_type_order[-1]
-object_paths = files_by_type[ext]
-
-params = defaults[ext]
-params.setdefault(OPT_CFLAGS, [])
-params.setdefault(OPT_LDFLAGS, [])
-
-action = LinkAction(object_paths)
-if not action.runIfNeeded():
-	sys.exit(1)
-
-sys.exit(0 if action.wait() == 0 else 1)
+for type in FileType.registered_types_in_order():
+	type.run_all_actions()