class TargetState(object):
	_dep_lock = None

	def __init__(self, p):
		self.path = p
	
	def __repr__(self):
		return 'TargetState(%r)' % (self.path,)
	
	@staticmethod
	def built_targets(dir):
		'''
		Returns the target names which have metadata stored in `dir`
		'''
		for f in os.listdir(dir):
			_log.trace("checking file %s", f);
			if f.startswith('deps.'):
				target_name = f[5:]
				_log.trace(".deps file: %s", target_name);
				yield target_name

	def meta_path(self, ext):
		base, target = os.path.split(self.path)
		meta_dir = os.path.join(base, META_DIR)
		return os.path.join(meta_dir, "%s.%s" % (ext, target))

	def _ensure_meta_path(self, ext):
		p = self.meta_path(ext)
		mkdirp(os.path.dirname(p))
		return p
	
	def _ensure_dep_lock(self):
		if not self._dep_lock:
			self._dep_lock = Lock(self._ensure_meta_path('deps-lock'))
		return self._dep_lock

	def deps(self):
		rv = None
		deps_path = self.meta_path('deps')
		if not os.path.exists(deps_path):
			_log.trace("Not loading missing deps at %s", deps_path)
			return rv

		with self._ensure_dep_lock().read():
			try:
				f = open(deps_path)
			except IOError as e:
				if e.errno != errno.ENOENT: raise
			else:
				try:
					with f:
						rv = Dependencies(self.path, f)
				except VersionMismatch as e:
					_log.debug("Ignoring stored dependencies from incompatible version: %s", deps_path)
				except Exception as e:
					_log.debug("Error loading %s: %s (assuming dirty)", deps_path, e)
		_log.trace("Loaded serialized state from %s: %r" % (deps_path, rv,))
		return rv

	def create_lock(self):
		if self.lockfile is None:
			self.lockfile = Lock(self.meta_path('lock'))

	def add_dependency(self, dep):
		lock = Lock(self.meta_path('deps2-lock'))
		_log.debug('add dep: %s -> %s' % (self.path, dep))
		with lock.write():
			with open(self.meta_path('deps2'), 'a') as f:
				dep.append_to(f)
	
	def mark_clobbers(self):
		self.add_dependency(ClobbersTarget())
	
	def perform_build(self, builder, do_build):
		exe = builder.path
		if not os.path.exists(exe):
			raise SafeError("Build script not found: %s" % (exe))
		def still_needs_build(deps):
			_log.trace("checking if %s still needs build after releasing lock" % self.path)
			return deps is None or (not deps.already_built())

		with self._ensure_dep_lock().write():
			deps = self.deps()
			if not still_needs_build(deps):
				return False

			builder_dep = BuilderDependency.relative_to_target(self.path,
				path=builder.realpath,
				mtime=get_mtime(builder.realpath))

			_log.trace("created dep %s from builder %r" % (builder_dep, exe))
			temp = self._ensure_meta_path('deps2')
			with open(temp, 'w') as f:
				Dependencies.init_file(f)
				builder_dep.append_to(f)
			try:
				built = do_build(deps)
			except:
				os.remove(temp)
				raise
			else:
				if built:
					# always track the build time
					built_time = get_mtime(self.path)
					if built_time is not None:
						with open(temp, 'a') as f:
							BuildTime(built_time).append_to(f)
					rename(temp, self.meta_path('deps'))
				return built class AlwaysRebuild(Dependency):
	tag = 'always:'
	num_fields = 0
	fields = []
	def is_dirty(self, _):
		_log.debug('DIRTY: always rebuild')
		return True class Checksum(Dependency):
	tag = 'checksum:'
	num_fields = 1

	def __init__(self, cs):
		self.value = cs
		self.fields = [cs]
	
	@staticmethod
	def _add_file(sh, f):
		if sh is None:
			import hashlib
			sh = hashlib.sha1()
		while True:
			b = f.read(4096)
			if not b: break
			sh.update(b)
		return sh

	@classmethod
	def from_stream(cls, f):
		sh = cls._add_file(None, f)
		return cls(sh.hexdigest())
	
	@classmethod
	def from_files(cls, filenames):
		sh = None
		for filename in filenames:
			with open(filename, 'rb') as f:
				sh = cls._add_file(sh, f)
		return cls(sh.hexdigest()) class FileDependency(BaseFileDependency):
	tag = 'file:'
	recursive = True

	def __init__(self, mtime, checksum, path):
		self.path = path
		self.checksum = checksum
		self.mtime = mtime

	def is_dirty(self, args):
		base = args.base
		path = self.full_path(base)

		def is_dirty_mtime():
			return self.mtime_mismatch(path)

		built, mtime_dirty = dirty_check_with_dep(path, is_dirty_mtime, args)

		if mtime_dirty or built:
			if self.checksum is None:
				return mtime_dirty
			else:
				# if mtime is unchanged after a build, try checksum anyway
				def checksum_dirty():
					_log.trace("%s: comparing using checksum %s", self.path, self.checksum)
					state = TargetState(path)
					deps = state.deps()
					checksum = deps and deps.checksum
					if checksum != self.checksum:
						_log.debug("DIRTY: %s (stored checksum is %s, current is %s)", self.path, self.checksum, checksum)
						return True
					return False

				_built, checksum_dirty = dirty_check_with_dep(path, checksum_dirty, args)
				return checksum_dirty
		else:
			return False META_DIR = '.gup' class Builder(object):
	'''
	The canonical builder for a target.
	`path` is the path to the build script, even if this
	builder was obtained indirectly (via a Gupfile match)
	'''
	def __init__(self, script_path, target, basedir, parent):
		self.path = script_path
		self.realpath = os.path.realpath(self.path)
		self.target = target
		self.basedir = basedir
		self.target_path = os.path.join(self.basedir, self.target)
		self.parent = parent
	
	def __repr__(self):
		return "Builder(path=%r, target=%r, basedir=%r, parent=%r)" % (self.path, self.target, self.basedir, self.parent)

	@staticmethod
	def for_target(path):
		for candidate in possible_gup_files(path):
			builder = candidate.get_builder()
			if builder is not None:
				return builder
		return None class Target(object):
	def __init__(self, builder):
		self.builder = builder
		self.path = self.builder.target_path
		self.state = TargetState(self.path)
	
	def __repr__(self):
		return 'Target(%r)' % (self.path,)
	
	def build_or_update(self, update):
		if update:
			built = _build_if_dirty(self, True)
			if not built:
				_log.trace("no build needed")
		else:
			self.perform_build(False)
			built = True

		return built

	def is_dirty(self):
		return _build_if_dirty(self, allow_build = False)

	def perform_build(self, from_update):
		self.state.perform_build(self.builder, lambda deps: self._perform_build(deps, from_update))

	def _perform_build(self, deps, from_update):
		'''
		Assumes locks are held (by state.perform_build)
		'''
		assert self.builder is not None
		assert os.path.exists(self.builder.path)
		exe_path = path.abspath(self.builder.path)
		exe_path_relative_to_cwd = os.path.relpath(exe_path,ROOT_CWD)

		# dest may not exist, if a /gup/ directory is in use
		basedir = self.builder.basedir
		mkdirp(basedir)

		env = os.environ.copy()
		env['GUP_TARGET'] = os.path.abspath(self.path)
		extend_build_env(env)

		target_relative_to_cwd = os.path.relpath(self.path, ROOT_CWD)

		output_file = os.path.abspath(self.state.meta_path('out'))
		try_remove(output_file)

		cleanup_output_file = True
		try:
			args = [exe_path, output_file, self.builder.target]
			_log.info(target_relative_to_cwd)
			mtime = get_mtime(self.path)

			exe = _guess_executable(exe_path)

			if exe is not None:
				args = exe + args

			if XTRACE:
				_log.info(' # %s'% (os.path.abspath(basedir),))
				_log.info(' + ' + ' '.join(map(quote, args)))
			else:
				_log.trace(' from cwd: %s'% (os.path.abspath(basedir),))
				_log.trace('executing: ' + ' '.join(map(quote, args)))

			try:
				ret = self._run_process(args, cwd = basedir, env = env)
			except OSError:
				if exe: raise # we only expect errors when we could deduce no executable
				raise SafeError("%s is not executable and has no shebang line" % (exe_path_relative_to_cwd))

			new_mtime = get_mtime(self.path)
			target_changed = mtime != new_mtime
			if target_changed:
				_log.trace("old_mtime=%r, new_mtime=%r" % (mtime, new_mtime))
				if not lisdir(self.path):
					# directories often need to be created directly
					self.state.mark_clobbers()
					expect_clobber = False if deps is None else deps.clobbers
					if not (from_update and expect_clobber):
						_log.warning("%s modified %s directly" % (exe_path_relative_to_cwd, self.path))
			if ret == 0:
				if os.path.lexists(output_file):
					if os.path.lexists(self.path) and (
						lisdir(self.path) or lisdir(output_file)
					):
						_log.trace("removing previous %s", self.path)
						try_remove(self.path)
					rename(output_file, self.path)
				else:
					if (not target_changed) and (os.path.lexists(self.path)) and (not os.path.islink(self.path)):
						_log.warning("Removing stale target: %s", target_relative_to_cwd)
						try_remove(self.path)
				cleanup_output_file = False # not needed
			else:
				temp_file = None
				if keep_failed_outputs():
					cleanup_output_file = False # not wanted
					if os.path.lexists(output_file):
						temp_file = os.path.relpath(output_file, ROOT_CWD)
				_log.trace("builder exited with status %s" % (ret,))
				raise TargetFailed(target_relative_to_cwd, ret, temp_file)
		finally:
			if cleanup_output_file:
				try_remove(output_file)
		return True

	
	def _run_process(self, args, cwd, env):
		try:
			proc = subprocess.Popen(args, cwd = cwd, env = env, close_fds=False)
		except OSError as e:
			if e.errno == errno.ENOENT:
				raise SafeError("Executable not found: %s" % (args[0],))
			raise e
		return proc.wait() def prepare_build(p):
	p = resolve_base(p)
	builder = Builder.for_target(p)
	_log.trace('prepare_build(%r) -> %r' % (p, builder))
	if builder is not None:
		return Target(builder)
	return None PLAIN  = "" def getLogger(*a):
	logger = logging.getLogger(*a)
	logger.addFilter(_color_filter)
	return logger TRACE_LVL = 5 INDENT = os.environ.get('GUP_INDENT', '') def set_verbosity(val):
	os.environ['GUP_VERBOSE'] = str(val) def set_keep_failed_outputs():
	os.environ['GUP_KEEP_FAILED'] = '1' DEFAULT_VERBOSITY = int(os.environ.get('GUP_VERBOSE', '0')) def set_trace():
	global XTRACE
	XTRACE = True
	os.environ['GUP_XTRACE'] = '1' PY3 = sys.version_info >= (3,0) IS_WINDOWS = sys.platform.startswith('win') def setup_jobserver(maxjobs):
	"Start the job server"
	global _jobserver
	if _jobserver is not None:
		_log.warning("tried to set up jobserver multiple times")
		return

	_debug('setup_jobserver(%s)' % maxjobs)
	if maxjobs is None:
		_jobserver = _discover_jobserver()

	if _jobserver is None:
		maxjobs = maxjobs or 1
		if maxjobs == 1:
			_debug("no need for a jobserver (--jobs=1)")
			_jobserver = SerialJobserver(maxjobs)
		else:
			_debug("new jobserver! %s" % (maxjobs))
			path = _create_named_pipe()
			_jobserver = NamedPipeJobserver(path, maxjobs) class Task(object):
	'''
	Each target we're asked to build is represented as a Task,
	so that they can be invoked in parallel
	'''
	def __init__(self, opts, parent_target, target_path):
		self.target_path = target_path
		self.opts = opts
		self.parent_target = parent_target
	
	def prepare(self):
		'''
		Returns:
			- None (not buildable),
			- Task (depends implicitly on another task; i.e. a symlink)
			- Target (buildable)
		'''

		target_path = self.target_path
		opts = self.opts

		target = self.target = prepare_build(target_path)
		if target is None:
			target_dest = None
			try:
				target_dest = os.readlink(target_path)
			except (OSError) as e:
				if e.errno in (errno.ENOENT, errno.EINVAL):
					# not a link
					pass
				else:
					raise

			if target_dest is not None:
				# this target isn't buildable, but its symlink destination might be
				if not os.path.isabs(target_dest):
					target_dest = os.path.join(os.path.dirname(target_path), target_dest)
				dest = Task(self.opts, self.parent_target, target_dest)
				return dest

			if opts.update and os.path.lexists(target_path):
				self.report_nobuild()
			else:
				raise Unbuildable(target_path)
		return target

	def build(self):
		'''
		run in a child process
		'''
		self.built = self.target.build_or_update(update=self.opts.update)
		self.complete()

	def complete(self):
		if self.parent_target is not None:
			intermediate_paths, target_path = traverse_from(os.getcwd(), self.target_path)
			mtime = get_mtime(target_path)

			if self.target:
				dep = FileDependency.of_target(self.parent_target, self.target, mtime=mtime)
			else:
				dep = FileDependency.relative_to_target(self.parent_target, mtime=mtime, path=self.target_path)

			state = TargetState(self.parent_target)
			state.add_dependency(dep)
			if intermediate_paths or True:
				_log.trace("adding intermediate paths: %r", intermediate_paths)
				for intermediate in intermediate_paths:
					dep = FileDependency.relative_to_target(
							self.parent_target,
							path=intermediate,
							mtime=get_mtime(intermediate))
					state.add_dependency(dep)
	
	def handle_result(self, rv):
		_log.trace("build process exited with status: %r" % (rv,))
		if rv == 0:
			return
		if rv == SafeError.exitcode:
			# already logged - just raise an empty exception to propagate exit code
			raise SafeError(None)
		else:
			raise RuntimeError("unknown error in child process - exit status %s" % rv)

	def report_nobuild(self):
		if IS_ROOT:
			_log.info("%s: up to date", self.target_path)
		else:
			_log.trace("%s: up to date", self.target_path) class TaskRunner(object):
	def __init__(self):
		self.tasks = []

	def add(self, fn):
		self.tasks.append(fn)

	def run(self):
		from .parallel import start_job, wait_all
		while self.tasks:
			task = self.tasks.pop(0)
			start_job(task.build, task.handle_result)
		wait_all() def resolve_base(p):
	return os.path.join(
		os.path.realpath(os.path.dirname(p)),
		os.path.basename(p)
	) def traverse_from(base, rel, resolve_final=False):
	if IS_WINDOWS:
		# yeah, nah
		return ([], os.path.join(base, rel))

	if os.path.isabs(rel):
		base = '/'

	# _log.trace("traverse_from: %s, %s", base, rel)
	links = []
	parts = [part for part in rel.split(os.path.sep) if part]
	path = base
	if not parts:
		return (links, path)

	while True:
		try:
			# _log.trace("readlink: %s", path)
			dest = os.readlink(path)
		except OSError as e:
			if e.errno == errno.EINVAL:
				# not a symlink; continue along path
				path = os.path.join(path, parts.pop(0))
				if not (parts or resolve_final):
					# _log.trace("returning because there's no parts left: %s, %s", links, path)
					return (links, path)
			elif e.errno == errno.ENOENT:
				# doesn't exist, return the entire
				# remaining path
				return (links, os.path.join(path, *parts))
			else:
				raise
		else:
			# _log.trace("->: %s", dest)
			links.append(path)
			if os.path.isabs(dest):
				path = dest
			else:
				# relative dest
				path = os.path.join(os.path.dirname(path), dest)
import sys
import logging
import optparse
import os
	import stat
		import errno
from .error import *
from .util import *
from .state import TargetState, AlwaysRebuild, Checksum, FileDependency, META_DIR
from .gupfile import Builder
from .builder import Target, prepare_build
from .log import PLAIN, getLogger, TRACE_LVL
from .var import INDENT, set_verbosity, set_keep_failed_outputs, DEFAULT_VERBOSITY, set_trace, PY3, IS_WINDOWS
from .parallel import setup_jobserver
from .task import Task, TaskRunner
from .version import VERSION
from .path import resolve_base, traverse_from
							# ok, we're in path
							break
					except OSError: pass
				else:
					# not found
					here = os.path.abspath(here)
					_log.trace('`gup` not in $PATH - adding %s' % (here,))
					os.environ['PATH'] = os.pathsep.join([here] + path_entries)

def _main(argv):
	p = None
	action = None

	try:
		cmd = argv[0]
	except IndexError:
		pass
	else:
		if cmd == '--clean':
			p = optparse.OptionParser('Usage: gup --clean [OPTIONS] [dir [...]]')
			p.add_option('-i', '--interactive', action='store_true', help='Ask for confirmation before removing files', default=False)
			p.add_option('-n', '--dry-run', action='store_false', dest='force', help='Just print files that would be removed')
			p.add_option('-f', '--force', action='store_true', help='Actually remove files')
			p.add_option('-m', '--metadata', action='store_true', help='Remove .gup metadata directories, but leave targets')
			action = _clean_targets
		elif cmd == '--contents':
			p = optparse.OptionParser('Usage: gup --contents [file=<stdin>]')
			action = _mark_contents
		elif cmd == '--always':
			p = optparse.OptionParser('Usage: gup --always')
			action = _mark_always
		elif cmd == '--leave':
			p = optparse.OptionParser('Usage: gup --leave')
			action = _mark_leave
		elif cmd == '--ifcreate':
			p = optparse.OptionParser('Usage: gup --ifcreate [file [...]]')
			action = _mark_ifcreate
		elif cmd == '--buildable':
			p = optparse.OptionParser('Usage: gup --buildable [file]')
			action = _test_buildable
		elif cmd == '--dirty':
			p = optparse.OptionParser('Usage: gup --dirty [file [...]]')
			action = _test_dirty
		elif cmd == '--features':
			p = optparse.OptionParser('Usage: gup --features')
			action = _list_features
		elif cmd == '--version':
			p = optparse.OptionParser('Usage: gup --version')
			action = _print_version
	
	if action is None:
		# default parser
		p = optparse.OptionParser('Usage: gup [action] [OPTIONS] [target [...]]\n\n' +
			'Actions: (if present, the action must be the first argument)\n'
			'  --clean      Clean any gup-built targets\n' +
			'  --buildable  Check if a target is buildable\n' +
			'  --dirty      Check if one or more targets are out of date\n' +
			'\n' +
			'Actions which can only be called from a buildscript:\n' +
			'  --always     Mark this target as always-dirty\n' +
			'  --leave      Mark this target as fresh, preventing removal (even if the file is unchanged)\n' +
			'  --ifcreate   Rebuild the current target if the given file(s) are created\n' +
			'  --contents   Checksum the contents of a file\n' +
			'\n' +
			'  (use gup <action> --help) for further details')

		p.add_option('-u', '--update', '--ifchange', dest='update', action='store_true', help='Only rebuild stale targets', default=False)
		p.add_option('-j', '--jobs', type='int', default=None, help="Number of concurrent jobs to run")
		p.add_option('-x', '--trace', action='store_true', help='Trace build script invocations (also sets $GUP_XTRACE=1)')
		p.add_option('--keep-failed', action='store_true', help='Keep temporary output files on failure')
		action = _build
		verbosity = None
	else:
		argv.pop(0)
		verbosity = 0

	p.add_option('-q', '--quiet', action='count', default=0, help='Decrease verbosity')
	p.add_option('-v', '--verbose', action='count', default=DEFAULT_VERBOSITY, help='Increase verbosity')

	opts, args = p.parse_args(argv)

	verbosity = opts.verbose - opts.quiet
	_init_logging(verbosity)

	if action is _build:
		_bin_init()

	_log.trace('argv: %r, action=%r', argv, action)
	args = [arg.rstrip(os.path.sep) for arg in args]
	action(opts, args)

def _get_parent_target():
	t = os.environ.get('GUP_TARGET', None)
	if t is not None:
		assert os.path.isabs(t)
	return t

def _assert_parent_target(action):
	p = _get_parent_target()
	if p is None:
		_log.warning("%s was used outside of a gup target; ignoring" % (action,))
		sys.exit(0)
	return p

def _mark_leave(opts, targets):
	assert len(targets) == 0, "no arguments expected"
	parent_target = _assert_parent_target('--keep')
	try:
		st = os.lstat(parent_target)
	except OSError as e:
		if e.errno == errno.ENOENT:
			return
		raise

	if not stat.S_ISLNK(st.st_mode):
		os.utime(parent_target, None)

def _mark_always(opts, targets):
	assert len(targets) == 0, "no arguments expected"
	parent_target = _assert_parent_target('--always')

	TargetState(parent_target).add_dependency(AlwaysRebuild())