From mboxrd@z Thu Jan 1 00:00:00 1970 Received: from pigeon.gentoo.org ([69.77.167.62] helo=lists.gentoo.org) by finch.gentoo.org with esmtp (Exim 4.60) (envelope-from ) id 1KvHFd-0006dL-Oz for garchives@archives.gentoo.org; Wed, 29 Oct 2008 20:02:46 +0000 Received: from pigeon.gentoo.org (localhost [127.0.0.1]) by pigeon.gentoo.org (Postfix) with SMTP id 3DA11E03BD; Wed, 29 Oct 2008 20:02:45 +0000 (UTC) Received: from smtp.gentoo.org (smtp.gentoo.org [140.211.166.183]) by pigeon.gentoo.org (Postfix) with ESMTP id BE348E03BD for ; Wed, 29 Oct 2008 20:02:44 +0000 (UTC) Received: from stork.gentoo.org (stork.gentoo.org [64.127.104.133]) (using TLSv1 with cipher AES256-SHA (256/256 bits)) (No client certificate requested) by smtp.gentoo.org (Postfix) with ESMTP id 1695164D4C for ; Wed, 29 Oct 2008 20:02:43 +0000 (UTC) Received: from grobian by stork.gentoo.org with local (Exim 4.69) (envelope-from ) id 1KvHFa-0002eW-TF for gentoo-commits@lists.gentoo.org; Wed, 29 Oct 2008 20:02:42 +0000 To: gentoo-commits@lists.gentoo.org From: "Fabian Groffen (grobian)" Subject: [gentoo-commits] portage r11744 - in main/branches/prefix: bin pym/_emerge pym/portage pym/portage/dbapi X-VCS-Repository: portage X-VCS-Revision: 11744 X-VCS-Files: main/branches/prefix/bin/repoman main/branches/prefix/pym/_emerge/__init__.py main/branches/prefix/pym/portage/__init__.py main/branches/prefix/pym/portage/dbapi/vartree.py X-VCS-Directories: bin pym/_emerge pym/portage pym/portage/dbapi X-VCS-Committer: grobian X-VCS-Committer-Name: Fabian Groffen Content-Type: text/plain; charset=UTF-8 Message-Id: Sender: Fabian Groffen Date: Wed, 29 Oct 2008 20:02:42 +0000 Precedence: bulk List-Post: List-Help: List-Unsubscribe: List-Subscribe: List-Id: Gentoo Linux mail X-BeenThere: gentoo-commits@lists.gentoo.org Content-Transfer-Encoding: quoted-printable X-Archives-Salt: c5b81e6a-32e5-408e-915a-ca4f89daeb3e X-Archives-Hash: 28beace32aac061d469239a08bc275f9 Author: grobian Date: 2008-10-29 20:02:41 +0000 (Wed, 29 Oct 2008) New Revision: 11744 Modified: main/branches/prefix/bin/repoman main/branches/prefix/pym/_emerge/__init__.py main/branches/prefix/pym/portage/__init__.py main/branches/prefix/pym/portage/dbapi/vartree.py Log: Merged from trunk -r11736:11743 | 11737 | Fix graph.get() so that it works as intended, returning th= e | | zmedico | node corresponding to the given key. = | =20 | 11738 | Remove manifest1 digest-* autoadd code. Thanks to grobian.= | | zmedico | = | =20 | 11739 | Update the auto-add message to say "Manifest" instead of = | | zmedico | "digests". Thanks to grobian. = | =20 | 11740 | Bug #238957 - When removing unneeded preserved libs inside= | | zmedico | dblink.unmerge(), use a digraph to properly track consumer= | | | relationships between preserved libs. This fixes cases whe= re | | | preserved libs failed to be removed due to being consumed = by | | | other preserved libs. = | =20 | 11741 | Fix $ROOT handling inside LinkageMap.findConsumers(). = | | zmedico | = | =20 | 11742 | Fix interaction between LinkageMap.rebuild() and the packa= ge | | zmedico | replacement process in order to avoid problems with stale = or | | | unaccounted NEEDED. This solves a LinkageMap corruption = | | | issue which caused findConsumers to return false positive = | | | inside dblink.unmerge(). = | =20 | 11743 | Make config.setcpv() store the ebuild metadata inside = | | zmedico | self.configdict["pkg"], and reuse this metadata inside = | | | doebuild() in order to avoid redundant portdbapi.aux_get()= | | | calls. = | Modified: main/branches/prefix/bin/repoman =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- main/branches/prefix/bin/repoman 2008-10-29 17:03:35 UTC (rev 11743) +++ main/branches/prefix/bin/repoman 2008-10-29 20:02:41 UTC (rev 11744) @@ -1665,17 +1665,9 @@ # It's a manifest... auto add myautoadd+=3D[myunadded[x]] del myunadded[x] - elif len(xs[-1])>=3D7: - if xs[-1][:7]=3D=3D"digest-": - del xs[-2] - myeb=3D"/".join(xs[:-1]+[xs[-1][7:]])+".ebuild" - if os.path.exists(myeb): - # Ebuild exists for digest... So autoadd it. - myautoadd+=3D[myunadded[x]] - del myunadded[x] - =09 + if myautoadd: - print ">>> Auto-Adding missing digests..." + print ">>> Auto-Adding missing Manifest(s)..." if options.pretend: if vcs =3D=3D "cvs": print "(cvs add "+" ".join(myautoadd)+")" Modified: main/branches/prefix/pym/_emerge/__init__.py =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- main/branches/prefix/pym/_emerge/__init__.py 2008-10-29 17:03:35 UTC = (rev 11743) +++ main/branches/prefix/pym/_emerge/__init__.py 2008-10-29 20:02:41 UTC = (rev 11744) @@ -10012,6 +10012,7 @@ # Since config.setcpv() isn't guaranteed to call config.reset() due to # performance reasons, call it here to make sure all settings from the # previous package get flushed out (such as PORTAGE_LOG_FILE). + temp_settings.reload() temp_settings.reset() return temp_settings =20 Modified: main/branches/prefix/pym/portage/__init__.py =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- main/branches/prefix/pym/portage/__init__.py 2008-10-29 17:03:35 UTC = (rev 11743) +++ main/branches/prefix/pym/portage/__init__.py 2008-10-29 20:02:41 UTC = (rev 11744) @@ -354,14 +354,14 @@ relationship to the parent, the relationship is left as hard.""" =09 if node not in self.nodes: - self.nodes[node] =3D ({}, {}) + self.nodes[node] =3D ({}, {}, node) self.order.append(node) =09 if not parent: return =09 if parent not in self.nodes: - self.nodes[parent] =3D ({}, {}) + self.nodes[parent] =3D ({}, {}, parent) self.order.append(parent) =09 if parent in self.nodes[node][1]: @@ -442,7 +442,10 @@ return node in self.nodes =20 def get(self, key, default=3DNone): - return self.nodes.get(key, default) + node_data =3D self.nodes.get(key, self) + if node_data is self: + return default + return node_data[2] =20 def all_nodes(self): """Return a list of all nodes in the graph""" @@ -504,7 +507,7 @@ clone =3D digraph() clone.nodes =3D {} for k, v in self.nodes.iteritems(): - clone.nodes[k] =3D (v[0].copy(), v[1].copy()) + clone.nodes[k] =3D (v[0].copy(), v[1].copy(), v[2]) clone.order =3D self.order[:] return clone =20 @@ -1952,19 +1955,33 @@ =20 if self.mycpv =3D=3D mycpv: return - ebuild_phase =3D self.get("EBUILD_PHASE") has_changed =3D False self.mycpv =3D mycpv + cat, pf =3D catsplit(mycpv) cp =3D dep_getkey(mycpv) cpv_slot =3D self.mycpv pkginternaluse =3D "" iuse =3D "" + env_configdict =3D self.configdict["env"] + pkg_configdict =3D self.configdict["pkg"] + previous_iuse =3D pkg_configdict.get("IUSE") + for k in ("CATEGORY", "PKGUSE", "PF", "PORTAGE_USE"): + env_configdict.pop(k, None) + pkg_configdict["CATEGORY"] =3D cat + pkg_configdict["PF"] =3D pf if mydb: if not hasattr(mydb, "aux_get"): - slot =3D mydb["SLOT"] - iuse =3D mydb["IUSE"] + pkg_configdict.update(mydb) else: - slot, iuse =3D mydb.aux_get(self.mycpv, ["SLOT", "IUSE"]) + aux_keys =3D [k for k in auxdbkeys \ + if not k.startswith("UNUSED_")] + for k, v in izip(aux_keys, mydb.aux_get(self.mycpv, aux_keys)): + pkg_configdict[k] =3D v + for k in pkg_configdict: + if k !=3D "USE": + env_configdict.pop(k, None) + slot =3D pkg_configdict["SLOT"] + iuse =3D pkg_configdict["IUSE"] if pkg is None: cpv_slot =3D "%s:%s" % (self.mycpv, slot) else: @@ -2059,22 +2076,13 @@ has_changed =3D True self.configdict["pkg"]["PKGUSE"] =3D self.puse[:] # For saving to PUSE= file self.configdict["pkg"]["USE"] =3D self.puse[:] # this gets appended= to USE - previous_iuse =3D self.configdict["pkg"].get("IUSE") - self.configdict["pkg"]["IUSE"] =3D iuse =20 - # Always set known good values for these variables, since - # corruption of these can cause problems: - cat, pf =3D catsplit(self.mycpv) - self.configdict["pkg"]["CATEGORY"] =3D cat - self.configdict["pkg"]["PF"] =3D pf - if has_changed: self.reset(keeping_pkg=3D1,use_cache=3Duse_cache) =20 - # If this is not an ebuild phase and reset() has not been called, - # it's safe to return early here if IUSE has not changed. - if not (has_changed or ebuild_phase) and \ - previous_iuse =3D=3D iuse: + # If reset() has not been called, it's safe to return + # early if IUSE has not changed. + if not has_changed and previous_iuse =3D=3D iuse: return =20 # Filter out USE flags that aren't part of IUSE. This has to @@ -2092,7 +2100,7 @@ self.configdict["pkg"]["PORTAGE_IUSE"] =3D regex =20 ebuild_force_test =3D self.get("EBUILD_FORCE_TEST") =3D=3D "1" - if ebuild_force_test and ebuild_phase and \ + if ebuild_force_test and \ not hasattr(self, "_ebuild_force_test_msg_shown"): self._ebuild_force_test_msg_shown =3D True writemsg("Forcing test.\n", noiselevel=3D-1) @@ -4707,12 +4715,7 @@ # so that the caller can override it. tmpdir =3D mysettings["PORTAGE_TMPDIR"] =20 - # This variable is a signal to setcpv where it triggers - # filtering of USE for the ebuild environment. - mysettings["EBUILD_PHASE"] =3D mydo - mysettings.backup_changes("EBUILD_PHASE") - - if mydo !=3D "depend": + if mydo !=3D "depend" and mycpv !=3D mysettings.mycpv: """For performance reasons, setcpv only triggers reset when it detects a package-specific change in config. For the ebuild environment, a reset call is forced in order to ensure that the @@ -4776,18 +4779,17 @@ mysettings["PORTAGE_QUIET"] =3D "1" =20 if mydo !=3D "depend": - eapi, mysettings["INHERITED"], mysettings["SLOT"], mysettings["RESTRIC= T"] =3D \ - mydbapi.aux_get(mycpv, ["EAPI", "INHERITED", "SLOT", "RESTRICT"]) + # Metadata vars such as EAPI and RESTRICT are + # set by the above config.setcpv() call. + eapi =3D mysettings["EAPI"] if not eapi_is_supported(eapi): # can't do anything with this. raise portage.exception.UnsupportedAPIException(mycpv, eapi) - mysettings.pop("EAPI", None) - mysettings.configdict["pkg"]["EAPI"] =3D eapi try: mysettings["PORTAGE_RESTRICT"] =3D " ".join(flatten( portage.dep.use_reduce(portage.dep.paren_reduce( - mysettings.get("RESTRICT","")), - uselist=3Dmysettings.get("USE","").split()))) + mysettings["RESTRICT"]), + uselist=3Dmysettings["PORTAGE_USE"].split()))) except portage.exception.InvalidDependString: # RESTRICT is validated again inside doebuild, so let this go mysettings["PORTAGE_RESTRICT"] =3D "" @@ -5648,20 +5650,35 @@ =20 mycpv =3D "/".join((mysettings["CATEGORY"], mysettings["PF"])) =20 - # Make sure we get the correct tree in case there are overlays. - mytree =3D os.path.realpath( - os.path.dirname(os.path.dirname(mysettings["O"]))) - useflags =3D mysettings["PORTAGE_USE"].split() - try: - alist =3D mydbapi.getFetchMap(mycpv, useflags=3Duseflags, mytree=3Dmy= tree) - aalist =3D mydbapi.getFetchMap(mycpv, mytree=3Dmytree) - except portage.exception.InvalidDependString, e: - writemsg("!!! %s\n" % str(e), noiselevel=3D-1) - writemsg("!!! Invalid SRC_URI for '%s'.\n" % mycpv, noiselevel=3D-1) - del e - return 1 - mysettings["A"] =3D " ".join(alist) - mysettings["AA"] =3D " ".join(aalist) + emerge_skip_distfiles =3D returnpid + # Only try and fetch the files if we are going to need them ... + # otherwise, if user has FEATURES=3Dnoauto and they run `ebuild clean + # unpack compile install`, we will try and fetch 4 times :/ + need_distfiles =3D not emerge_skip_distfiles and \ + (mydo in ("fetch", "unpack") or \ + mydo not in ("digest", "manifest") and "noauto" not in features) + alist =3D mysettings.configdict["pkg"].get("A") + aalist =3D mysettings.configdict["pkg"].get("AA") + if need_distfiles or alist is None or aalist is None: + # Make sure we get the correct tree in case there are overlays. + mytree =3D os.path.realpath( + os.path.dirname(os.path.dirname(mysettings["O"]))) + useflags =3D mysettings["PORTAGE_USE"].split() + try: + alist =3D mydbapi.getFetchMap(mycpv, useflags=3Duseflags, + mytree=3Dmytree) + aalist =3D mydbapi.getFetchMap(mycpv, mytree=3Dmytree) + except portage.exception.InvalidDependString, e: + writemsg("!!! %s\n" % str(e), noiselevel=3D-1) + writemsg("!!! Invalid SRC_URI for '%s'.\n" % mycpv, + noiselevel=3D-1) + del e + return 1 + mysettings.configdict["pkg"]["A"] =3D " ".join(alist) + mysettings.configdict["pkg"]["AA"] =3D " ".join(aalist) + else: + alist =3D set(alist.split()) + aalist =3D set(aalist.split()) if ("mirror" in features) or fetchall: fetchme =3D aalist checkme =3D aalist @@ -5674,12 +5691,7 @@ # so do not check them again. checkme =3D [] =20 - # Only try and fetch the files if we are going to need them ... - # otherwise, if user has FEATURES=3Dnoauto and they run `ebuild clean - # unpack compile install`, we will try and fetch 4 times :/ - need_distfiles =3D (mydo in ("fetch", "unpack") or \ - mydo not in ("digest", "manifest") and "noauto" not in features) - emerge_skip_distfiles =3D returnpid + if not emerge_skip_distfiles and \ need_distfiles and not fetch( fetchme, mysettings, listonly=3Dlistonly, fetchonly=3Dfetchonly): @@ -5873,8 +5885,7 @@ misc_keys =3D ["LICENSE", "PROPERTIES", "PROVIDE", "RESTRICT", "SRC_URI= "] other_keys =3D ["SLOT"] all_keys =3D dep_keys + misc_keys + other_keys - metadata =3D dict(izip(all_keys, - mydbapi.aux_get(mysettings.mycpv, all_keys))) + metadata =3D mysettings.configdict["pkg"] =20 class FakeTree(object): def __init__(self, mydb): Modified: main/branches/prefix/pym/portage/dbapi/vartree.py =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D= =3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D=3D --- main/branches/prefix/pym/portage/dbapi/vartree.py 2008-10-29 17:03:35= UTC (rev 11743) +++ main/branches/prefix/pym/portage/dbapi/vartree.py 2008-10-29 20:02:41= UTC (rev 11744) @@ -23,7 +23,7 @@ grabfile, grabdict, normalize_path, new_protect_filename, getlibpaths from portage.versions import pkgsplit, catpkgsplit, catsplit, best, pkgc= mp =20 -from portage import listdir, dep_expand, flatten, key_expand, \ +from portage import listdir, dep_expand, digraph, flatten, key_expand, \ doebuild_environment, doebuild, env_update, prepare_build_dirs, \ abssymlink, movefile, _movefile, bsd_chflags, cpv_getkey =20 @@ -208,13 +208,25 @@ """ return isinstance(self._key, tuple) =20 - def rebuild(self, include_file=3DNone): + class _LibGraphNode(_ObjectKey): + __slots__ =3D ("alt_paths",) + + def __init__(self, obj, root): + LinkageMap._ObjectKey.__init__(self, obj, root) + self.alt_paths =3D set() + + def __str__(self): + return str(sorted(self.alt_paths)) + + def rebuild(self, exclude_pkgs=3DNone, include_file=3DNone): root =3D self._root libs =3D {} obj_key_cache =3D {} obj_properties =3D {} lines =3D [] for cpv in self._dbapi.cpv_all(): + if exclude_pkgs is not None and cpv in exclude_pkgs: + continue lines +=3D self._dbapi.aux_get(cpv, ["NEEDED.ELF.2"])[0].split('\n') # Cache NEEDED.* files avoid doing excessive IO for every rebuild. self._dbapi.flush_cache() @@ -565,7 +577,8 @@ raise KeyError("%s (%s) not in object list" % (obj_key, obj)) =20 # Determine the directory(ies) from the set of objects. - objs_dirs =3D set([os.path.dirname(x) for x in objs]) + objs_dirs =3D set(os.path.join(self._root, + os.path.dirname(x).lstrip(os.sep)) for x in objs) =20 # If there is another version of this lib with the # same soname and the master link points to that @@ -2349,7 +2362,8 @@ writemsg("!!! FAILED prerm: %s\n" % retval, noiselevel=3D-1) =20 self._unmerge_pkgfiles(pkgfiles, others_in_slot) - =09 + self._clear_contents_cache() + # Remove the registration of preserved libs for this pkg instance plib_registry =3D self.vartree.dbapi.plib_registry plib_registry.unregister(self.mycpv, self.settings["SLOT"], @@ -2369,64 +2383,67 @@ if retval !=3D os.EX_OK: writemsg("!!! FAILED postrm: %s\n" % retval, noiselevel=3D-1) =20 - # regenerate reverse NEEDED map - self.vartree.dbapi.linkmap.rebuild() + # Skip this if another package in the same slot has just been + # merged on top of this package, since the other package has + # already called LinkageMap.rebuild() and passed it's NEEDED file + # in as an argument. + if not others_in_slot: + self.vartree.dbapi.linkmap.rebuild(exclude_pkgs=3D(self.mycpv,)) =20 # remove preserved libraries that don't have any consumers left - # FIXME: this code is quite ugly and can likely be optimized in sever= al ways + # Since preserved libraries can be consumers of other preserved + # libraries, use a graph to track consumer relationships. plib_dict =3D plib_registry.getPreservedLibs() - for cpv in plib_dict: - plib_dict[cpv].sort() - # for the loop below to work correctly, we need all - # symlinks to come before the actual files, such that - # the recorded symlinks (sonames) will be resolved into - # their real target before the object is found not to be - # in the reverse NEEDED map - def symlink_compare(x, y): - x =3D os.path.join(self.myroot, x.lstrip(os.path.sep)) - y =3D os.path.join(self.myroot, y.lstrip(os.path.sep)) - if os.path.islink(x): - if os.path.islink(y): - return 0 - else: - return -1 - elif os.path.islink(y): - return 1 + lib_graph =3D digraph() + preserved_nodes =3D set() + root =3D self.myroot + for plibs in plib_dict.itervalues(): + for f in plibs: + preserved_node =3D LinkageMap._LibGraphNode(f, root) + if not preserved_node.file_exists(): + continue + existing_node =3D lib_graph.get(preserved_node) + if existing_node is not None: + preserved_node =3D existing_node else: - return 0 + lib_graph.add(preserved_node, None) + preserved_node.alt_paths.add(f) + preserved_nodes.add(preserved_node) + for c in self.vartree.dbapi.linkmap.findConsumers(f): + consumer_node =3D LinkageMap._LibGraphNode(c, root) + if not consumer_node.file_exists(): + continue + # Note that consumers may also be providers. + existing_node =3D lib_graph.get(consumer_node) + if existing_node is not None: + consumer_node =3D existing_node + consumer_node.alt_paths.add(c) + lib_graph.add(preserved_node, consumer_node) =20 - plib_dict[cpv].sort(symlink_compare) - for f in plib_dict[cpv]: - f_abs =3D os.path.join(self.myroot, f.lstrip(os.path.sep)) - if not os.path.exists(f_abs): - continue - unlink_list =3D [] - consumers =3D self.vartree.dbapi.linkmap.findConsumers(f) - if not consumers: - unlink_list.append(f_abs) + while not lib_graph.empty(): + root_nodes =3D preserved_nodes.intersection(lib_graph.root_nodes()) + if not root_nodes: + break + lib_graph.difference_update(root_nodes) + unlink_list =3D set() + for node in root_nodes: + unlink_list.update(node.alt_paths) + unlink_list =3D sorted(unlink_list) + for obj in unlink_list: + obj =3D os.path.join(root, obj.lstrip(os.sep)) + if os.path.islink(obj): + obj_type =3D "sym" else: - keep=3DFalse - for c in consumers: - c =3D os.path.join(self.myroot, - c.lstrip(os.path.sep)) - if c not in self.getcontents(): - keep=3DTrue - break - if not keep: - unlink_list.append(f_abs) - for obj in unlink_list: - try: - if os.path.islink(obj): - obj_type =3D "sym" - else: - obj_type =3D "obj" - os.unlink(obj) - showMessage("<<< !needed %s %s\n" % (obj_type, obj)) - except OSError, e: - if e.errno =3D=3D errno.ENOENT: - pass - else: - raise e + obj_type =3D "obj" + try: + os.unlink(obj) + except OSError, e: + if e.errno !=3D errno.ENOENT: + raise + del e + else: + showMessage("<<< !needed %s %s\n" % (obj_type, obj)) + plib_registry.pruneNonExisting() =09 finally: @@ -3556,6 +3573,10 @@ gid=3Dportage_gid, mode=3D02750, mask=3D02) writedict(cfgfiledict, conf_mem_file) =20 + exclude_pkgs =3D set(dblnk.mycpv for dblnk in others_in_slot) + self.vartree.dbapi.linkmap.rebuild(exclude_pkgs=3Dexclude_pkgs, + include_file=3Dos.path.join(inforoot, "NEEDED.ELF.2")) + # These caches are populated during collision-protect and the data # they contain is now invalid. It's very important to invalidate # the contents_inodes cache so that FEATURES=3Dunmerge-orphans