Skip to content

Commit

Permalink
Merge branch 'feature/1248-rm_pkg_fam'
Browse files Browse the repository at this point in the history
  • Loading branch information
nerdvegas committed Mar 22, 2022
2 parents d4b303f + 2321344 commit 90b6327
Show file tree
Hide file tree
Showing 7 changed files with 175 additions and 5 deletions.
43 changes: 42 additions & 1 deletion src/rez/cli/rm.py
Original file line number Diff line number Diff line change
Expand Up @@ -15,6 +15,13 @@ def setup_parser(parser, completions=False):
"-p", "--package",
help="remove the specified package (eg 'foo-1.2.3'). This will work "
"even if the package is currently ignored.")
group.add_argument(
"-f", "--family",
help="remove the specified package family (eg 'python'). This is only "
"supported if the family is empty")
group.add_argument(
"--force-family", action="store_true",
help="like -f, but delete package family even if not empty")
group.add_argument(
"-i", "--ignored-since", type=int, metavar="DAYS",
help="remove all packages that have been ignored for >= DAYS")
Expand All @@ -24,7 +31,8 @@ def setup_parser(parser, completions=False):
help="dry run mode")
parser.add_argument(
"PATH", nargs='?',
help="the repository containing the package(s) to remove.")
help="the repository containing the package(s) or package family "
" to remove.")


def remove_package(opts, parser):
Expand All @@ -46,6 +54,35 @@ def remove_package(opts, parser):
sys.exit(1)


def remove_package_family(opts, parser, force=False):
from rez.vendor.version.requirement import VersionedObject
from rez.package_remove import remove_package_family
from rez.exceptions import PackageRepositoryError

if opts.dry_run:
parser.error("--dry-run is not supported with --family")

if not opts.PATH:
parser.error("Must specify PATH with --family")

obj = VersionedObject(opts.family)
if obj.version:
parser.error("Expected package name, not version")

success = False
try:
success = remove_package_family(obj.name, opts.PATH, force=force)
except PackageRepositoryError as e:
print("Error: %s" % e, file=sys.stderr)
sys.exit(1)

if success:
print("Package family removed.")
else:
print("Package family not found.", file=sys.stderr)
sys.exit(1)


def remove_ignored_since(opts, parser):
from rez.package_remove import remove_packages_ignored_since

Expand Down Expand Up @@ -73,6 +110,10 @@ def remove_ignored_since(opts, parser):
def command(opts, parser, extra_arg_groups=None):
if opts.package:
remove_package(opts, parser)
elif opts.family:
remove_package_family(opts, parser)
elif opts.force_family:
remove_package_family(opts, parser, force=True)
elif opts.ignored_since is not None:
remove_ignored_since(opts, parser)
else:
Expand Down
1 change: 1 addition & 0 deletions src/rez/data/tests/packages/py_packages/empty/README.md
Original file line number Diff line number Diff line change
@@ -0,0 +1 @@
A deliberately empty package family.
18 changes: 18 additions & 0 deletions src/rez/package_remove.py
Original file line number Diff line number Diff line change
Expand Up @@ -12,6 +12,24 @@
basestring = six.string_types[0]


def remove_package_family(name, path, force=False):
"""Remove a package family from its repository.
A family can only be deleted if it contains no packages, hidden or
otherwise, unless `force` is True.
Args:
name (str): Name of package family.
path (str): Package repository path containing the package family.
force (bool): If True, delete family even if not empty.
Returns:
bool: True if the package family was removed, False if not found.
"""
repo = package_repository_manager.get_repository(path)
return repo.remove_package_family(name, force=force)


def remove_package(name, version, path):
"""Remove a package from its repository.
Expand Down
12 changes: 12 additions & 0 deletions src/rez/package_repository.py
Original file line number Diff line number Diff line change
Expand Up @@ -269,6 +269,18 @@ def remove_package(self, pkg_name, pkg_version):
"""
raise NotImplementedError

def remove_package_family(self, pkg_name, force=False):
"""Remove an empty package family.
Args:
pkg_name (str): Package name
force (bool): If Trur, delete even if not empty.
Returns:
bool: True if the family was removed, False if it wasn't found.
"""
raise NotImplementedError

def remove_ignored_since(self, days, dry_run=False, verbose=False):
"""Remove packages ignored for >= specified number of days.
Expand Down
18 changes: 18 additions & 0 deletions src/rez/packages.py
Original file line number Diff line number Diff line change
Expand Up @@ -598,6 +598,24 @@ def get_package(name, version, paths=None):
return None


def get_package_family_from_repository(name, path):
"""Get a package family from a repository.
Args:
name (str): Name of the package, eg 'maya'.
Returns:
`PackageFamily` object, or None if the family was not found.
"""
repo = package_repository_manager.get_repository(path)

family_resource = repo.get_package_family(name)
if family_resource is None:
return None

return PackageFamily(family_resource)


def get_package_from_repository(name, version, path):
"""Get a package from a repository.
Expand Down
54 changes: 51 additions & 3 deletions src/rez/tests/test_packages.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,11 +7,14 @@
"""
from rez.packages import iter_package_families, iter_packages, get_package, \
create_package, get_developer_package, get_variant_from_uri, \
get_package_from_uri, get_package_from_repository
get_package_from_uri, get_package_from_repository, \
get_package_family_from_repository
from rez.exceptions import PackageRepositoryError
from rez.package_py_utils import expand_requirement
from rez.package_resources import package_release_keys
from rez.package_move import move_package
from rez.package_remove import remove_package, remove_packages_ignored_since
from rez.package_remove import remove_package, remove_packages_ignored_since, \
remove_package_family
from rez.package_repository import package_repository_manager
from rez.tests.util import TestBase, TempdirMixin
from rez.utils.formatting import PackageRequest
Expand Down Expand Up @@ -58,7 +61,10 @@
])


ALL_FAMILIES = set(x.split('-')[0] for x in ALL_PACKAGES)
ALL_FAMILIES = set(
[x.split('-')[0] for x in ALL_PACKAGES]
+ ["empty"]
)


def _to_names(it):
Expand Down Expand Up @@ -544,6 +550,48 @@ def test_package_remove(self):
i = repo.unignore_package(pkg_name, pkg_version)
self.assertEqual(i, -1)

def test_package_family_remove(self):
"""Test package family remove."""
pkg_name = "pydad"

# copy packages to a temp repo
repo_path = os.path.join(self.root, "tmp6_packages")
shutil.copytree(self.solver_packages_path, repo_path)

# verify that source fam exists
src_fam = get_package_family_from_repository(pkg_name, repo_path)
self.assertNotEqual(src_fam, None)

# remove it, will fail as not empty
with self.assertRaises(PackageRepositoryError):
remove_package_family(pkg_name, repo_path)

# remove all pydad packages
versions = [pkg.version for pkg in src_fam.iter_packages()]
for version in versions:
self.assertTrue(remove_package(pkg_name, version, repo_path))

# now it should remove successfully
self.assertTrue(remove_package_family(pkg_name, repo_path))

# verify that the fam no longer exists
self.assertFalse(remove_package_family(pkg_name, repo_path))

pkg_name2 = "pymum"

# get another fam
src_fam2 = get_package_family_from_repository(pkg_name2, repo_path)
self.assertNotEqual(src_fam2, None)

# force remove another fam
self.assertTrue(remove_package_family(pkg_name2, repo_path, force=True))

# verify that the fam no longer exists
self.assertEqual(
get_package_family_from_repository(pkg_name2, repo_path),
None
)

def test_remove_packages_ignored_since(self):
pkg_name = "pydad"
pkg_version = Version("2")
Expand Down
34 changes: 33 additions & 1 deletion src/rezplugins/package_repository/filesystem.py
Original file line number Diff line number Diff line change
Expand Up @@ -735,6 +735,36 @@ def remove_package(self, pkg_name, pkg_version):

return True

def remove_package_family(self, pkg_name, force=False):
# get a non-cached copy and see if fam exists
repo_copy = self._copy(
disable_pkg_ignore=True,
disable_memcache=True
)

fam = repo_copy.get_package_family(pkg_name)
if fam is None:
return False

# check that the pkg fam is empty
if not force:
empty = True
for _ in repo_copy.iter_packages(fam):
empty = False
break

if not empty:
raise PackageRepositoryError(
"Cannot remove non-empty package family %r" % pkg_name
)

# delete the fam dir
fam_dir = os.path.join(self.location, pkg_name)
shutil.rmtree(fam_dir)

self._on_changed(pkg_name)
return True

def remove_ignored_since(self, days, dry_run=False, verbose=False):
now = int(time.time())
num_removed = 0
Expand Down Expand Up @@ -1464,7 +1494,9 @@ def _on_changed(self, pkg_name):
# stats are required to determine if a resolve cache entry is stale.
#
family_path = os.path.join(self.location, pkg_name)
os.utime(family_path, None)

if os.path.exists(family_path):
os.utime(family_path, None)

# clear internal caches, otherwise change may not be visible
self.clear_caches()
Expand Down

0 comments on commit 90b6327

Please sign in to comment.