Optimized the code for bug #288083 and make it handle more cases. Now Package
instances have 'visible' and 'masks' attributes, since this information needs
to be accessed in more places now.

svn path=/main/trunk/; revision=14859
diff --git a/pym/_emerge/Package.py b/pym/_emerge/Package.py
index dc8b975..6ab9be7 100644
--- a/pym/_emerge/Package.py
+++ b/pym/_emerge/Package.py
@@ -22,8 +22,8 @@
 		"installed", "metadata", "onlydeps", "operation",
 		"root_config", "type_name",
 		"category", "counter", "cp", "cpv_split",
-		"inherited", "invalid", "iuse", "mtime",
-		"pf", "pv_split", "root", "slot", "slot_atom",) + \
+		"inherited", "invalid", "iuse", "masks", "mtime",
+		"pf", "pv_split", "root", "slot", "slot_atom", "visible",) + \
 	("_use",)
 
 	metadata_keys = [
@@ -50,6 +50,83 @@
 		self.category, self.pf = portage.catsplit(self.cpv)
 		self.cpv_split = portage.catpkgsplit(self.cpv)
 		self.pv_split = self.cpv_split[1:]
+		self.masks = self._masks()
+		self.visible = self._visible(self.masks)
+
+	def _masks(self):
+		masks = {}
+		settings = self.root_config.settings
+
+		if self.invalid is not None:
+			masks['invalid'] = self.invalid
+
+		if not settings._accept_chost(self.cpv, self.metadata):
+			masks['CHOST'] = self.metadata['CHOST']
+
+		eapi = self.metadata["EAPI"]
+		if not portage.eapi_is_supported(eapi):
+			masks['EAPI.unsupported'] = eapi
+		if portage._eapi_is_deprecated(eapi):
+			masks['EAPI.deprecated'] = eapi
+
+		missing_keywords = settings._getMissingKeywords(
+			self.cpv, self.metadata)
+		if missing_keywords:
+			masks['KEYWORDS'] = missing_keywords
+
+		try:
+			missing_properties = settings._getMissingProperties(
+				self.cpv, self.metadata)
+			if missing_properties:
+				masks['PROPERTIES'] = missing_properties
+		except portage.exception.InvalidDependString:
+			# already recorded as 'invalid'
+			pass
+
+		mask_atom = settings._getMaskAtom(self.cpv, self.metadata)
+		if mask_atom is not None:
+			masks['package.mask'] = mask_atom
+
+		system_mask = settings._getProfileMaskAtom(
+			self.cpv, self.metadata)
+		if system_mask is not None:
+			masks['profile.system'] = system_mask
+
+		try:
+			missing_licenses = settings._getMissingLicenses(
+				self.cpv, self.metadata)
+			if missing_licenses:
+				masks['LICENSE'] = missing_licenses
+		except portage.exception.InvalidDependString:
+			# already recorded as 'invalid'
+			pass
+
+		if not masks:
+			masks = None
+
+		return masks
+
+	def _visible(self, masks):
+
+		if masks is not None:
+
+			if 'EAPI.unsupported' in masks:
+				return False
+
+			if not self.installed and ( \
+				'invalid' in masks or \
+				'CHOST' in masks or \
+				'EAPI.deprecated' in masks or \
+				'KEYWORDS' in masks or \
+				'PROPERTIES' in masks):
+				return False
+
+			if 'package.mask' in masks or \
+				'profile.system' in masks or \
+				'LICENSE' in masks:
+				return False
+
+		return True
 
 	def _invalid_metadata(self, msg_type, msg):
 		if self.invalid is None:
diff --git a/pym/_emerge/depgraph.py b/pym/_emerge/depgraph.py
index dc7b7da..966e27b 100644
--- a/pym/_emerge/depgraph.py
+++ b/pym/_emerge/depgraph.py
@@ -49,7 +49,6 @@
 from _emerge.SetArg import SetArg
 from _emerge.show_invalid_depstring_notice import show_invalid_depstring_notice
 from _emerge.UnmergeDepPriority import UnmergeDepPriority
-from _emerge.visible import visible
 
 if sys.hexversion >= 0x3000000:
 	basestring = str
@@ -74,6 +73,7 @@
 		self.roots = {}
 		# All Package instances
 		self._pkg_cache = {}
+		self._highest_license_masked = {}
 		for myroot in trees:
 			self.trees[myroot] = {}
 			# Create a RootConfig instance that references
@@ -989,6 +989,7 @@
 				self._dynamic_config._slot_pkg_map[pkg.root][pkg.slot_atom] = pkg
 				self._dynamic_config.mydbapi[pkg.root].cpv_inject(pkg)
 				self._dynamic_config._filtered_trees[pkg.root]["porttree"].dbapi._clear_cache()
+				self._check_masks(pkg)
 
 			if not pkg.installed:
 				# Allow this package to satisfy old-style virtuals in case it
@@ -1047,6 +1048,17 @@
 			dep_stack.append(pkg)
 		return 1
 
+	def _check_masks(self, pkg):
+
+		slot_key = (pkg.root, pkg.slot_atom)
+
+		# Check for upgrades in the same slot that are
+		# masked due to a LICENSE change in a newer
+		# version that is not masked for any other reason.
+		other_pkg = self._frozen_config._highest_license_masked.get(slot_key)
+		if other_pkg is not None and pkg < other_pkg:
+			self._dynamic_config._masked_license_updates.add(other_pkg)
+
 	def _add_parent_atom(self, pkg, parent_atom):
 		parent_atoms = self._dynamic_config._parent_atoms.get(pkg)
 		if parent_atoms is None:
@@ -2350,7 +2362,7 @@
 		pkg, existing = ret
 		if pkg is not None:
 			settings = pkg.root_config.settings
-			if visible(settings, pkg) and not (pkg.installed and \
+			if pkg.visible and not (pkg.installed and \
 				settings._getMissingKeywords(pkg.cpv, pkg.metadata)):
 				self._dynamic_config._visible_pkgs[pkg.root].cpv_inject(pkg)
 		return ret
@@ -2425,12 +2437,9 @@
 						# here, packages that have been masked since they
 						# were installed can be automatically downgraded
 						# to an unmasked version.
-						try:
-							if not visible(pkgsettings, pkg):
-								continue
-						except portage.exception.InvalidDependString:
-							if not installed:
-								continue
+
+						if not pkg.visible:
+							continue
 
 						# Enable upgrade or downgrade to a version
 						# with visible KEYWORDS when the installed
@@ -2463,7 +2472,7 @@
 									except portage.exception.PackageNotFound:
 										continue
 									else:
-										if not visible(pkgsettings, pkg_eb):
+										if not pkg_eb.visible:
 											continue
 
 					# Calculation of USE for unbuilt ebuilds is relatively
@@ -2762,6 +2771,14 @@
 				installed=installed, metadata=metadata, onlydeps=onlydeps,
 				root_config=root_config, type_name=type_name)
 			self._frozen_config._pkg_cache[pkg] = pkg
+
+			if not pkg.visible and \
+				'LICENSE' in pkg.masks and len(pkg.masks) == 1:
+				slot_key = (pkg.root, pkg.slot_atom)
+				other_pkg = self._frozen_config._highest_license_masked.get(slot_key)
+				if other_pkg is None or pkg > other_pkg:
+					self._frozen_config._highest_license_masked[slot_key] = pkg
+
 		return pkg
 
 	def _validate_blockers(self):
@@ -2806,35 +2823,11 @@
 					# packages masked by license, since the user likely wants
 					# to adjust ACCEPT_LICENSE.
 					if pkg in final_db:
-						if pkg_in_graph and not visible(pkgsettings, pkg):
+						if not pkg.visible and \
+							(pkg_in_graph or 'LICENSE' in pkg.masks):
 							self._dynamic_config._masked_installed.add(pkg)
-						elif pkgsettings._getMissingLicenses(pkg.cpv, pkg.metadata):
-							self._dynamic_config._masked_installed.add(pkg)
-						elif pkg_in_graph or complete or deep:
-							# Check for upgrades in the same slot that are
-							# masked due to a LICENSE change in a newer
-							# version that is not masked for any other reason.
-							# Only do this for packages that are already in
-							# the graph, or complete or deep graphs, since
-							# otherwise it is likely a waste of time.
-							got_mask = False
-							for db, pkg_type, built, installed, db_keys in dbs:
-								if installed:
-									continue
-								if got_mask:
-									break
-								for upgrade_pkg in self._iter_match_pkgs(
-									root_config, pkg_type, pkg.slot_atom):
-									if upgrade_pkg <= pkg:
-										break
-									if not visible(pkgsettings,
-										upgrade_pkg, ignore=('LICENSE',)):
-										continue
-									if pkgsettings._getMissingLicenses(
-										upgrade_pkg.cpv, upgrade_pkg.metadata):
-										self._dynamic_config._masked_license_updates.add(upgrade_pkg)
-										got_mask = True
-										break
+						else:
+							self._check_masks(pkg)
 
 					blocker_atoms = None
 					blockers = None
@@ -4992,8 +4985,7 @@
 					continue
 				raise
 
-			if "merge" == pkg.operation and \
-				not visible(root_config.settings, pkg):
+			if "merge" == pkg.operation and not pkg.visible:
 				if skip_masked:
 					masked_tasks.append(Dependency(root=pkg.root, parent=pkg))
 				else:
@@ -5265,13 +5257,8 @@
 				arg = None
 			if arg:
 				return False
-		if pkg.installed:
-			try:
-				if not visible(
-					self._depgraph._frozen_config.pkgsettings[pkg.root], pkg):
-					return False
-			except portage.exception.InvalidDependString:
-				pass
+		if pkg.installed and not pkg.visible:
+			return False
 		in_graph = self._depgraph._dynamic_config._slot_pkg_map[
 			self._root].get(pkg.slot_atom)
 		if in_graph is None:
diff --git a/pym/_emerge/search.py b/pym/_emerge/search.py
index e5ad91d..f36a249 100644
--- a/pym/_emerge/search.py
+++ b/pym/_emerge/search.py
@@ -11,7 +11,6 @@
 from portage.util import writemsg_stdout
 
 from _emerge.Package import Package
-from _emerge.visible import visible
 
 class search(object):
 
@@ -111,9 +110,10 @@
 			pkg_type = "installed"
 		elif built:
 			pkg_type = "binary"
-		return visible(self.settings,
-			Package(type_name=pkg_type, root_config=self.root_config,
-			cpv=cpv, built=built, installed=installed, metadata=metadata))
+		return Package(type_name=pkg_type,
+			root_config=self.root_config,
+			cpv=cpv, built=built, installed=installed,
+			metadata=metadata).visible
 
 	def _xmatch(self, level, atom):
 		"""
diff --git a/pym/_emerge/visible.py b/pym/_emerge/visible.py
deleted file mode 100644
index c50768d..0000000
--- a/pym/_emerge/visible.py
+++ /dev/null
@@ -1,45 +0,0 @@
-# Copyright 1999-2009 Gentoo Foundation
-# Distributed under the terms of the GNU General Public License v2
-# $Id$
-
-import portage
-
-def visible(pkgsettings, pkg, ignore=None):
-	"""
-	Check if a package is visible. This can raise an InvalidDependString
-	exception if LICENSE is invalid.
-	TODO: optionally generate a list of masking reasons
-	@rtype: Boolean
-	@returns: True if the package is visible, False otherwise.
-	"""
-	if not pkg.metadata["SLOT"]:
-		return False
-	if not pkg.installed:
-		if pkg.invalid:
-			return False
-		if not pkgsettings._accept_chost(pkg.cpv, pkg.metadata):
-			return False
-	eapi = pkg.metadata["EAPI"]
-	if not portage.eapi_is_supported(eapi):
-		return False
-	if not pkg.installed:
-		if portage._eapi_is_deprecated(eapi):
-			return False
-		if pkgsettings._getMissingKeywords(pkg.cpv, pkg.metadata):
-			return False
-		try:
-			if pkgsettings._getMissingProperties(pkg.cpv, pkg.metadata):
-				return False
-		except portage.exception.InvalidDependString:
-			return False
-	if pkgsettings._getMaskAtom(pkg.cpv, pkg.metadata):
-		return False
-	if pkgsettings._getProfileMaskAtom(pkg.cpv, pkg.metadata):
-		return False
-	try:
-		if pkgsettings._getMissingLicenses(pkg.cpv, pkg.metadata):
-			if ignore is None or 'LICENSE' not in ignore:
-				return False
-	except portage.exception.InvalidDependString:
-		return False
-	return True