| | |
| | | error=str(e), |
| | | ) |
| | | |
| | | async def add_package(self, package: str) -> BuildResult: |
| | | async def add_package(self, package: str, include_repo: bool = False) -> BuildResult: |
| | | """Add and build (or download) a new package with dependencies. |
| | | |
| | | Args: |
| | | package: Package name |
| | | include_repo: Whether to check the managed repository for existing packages |
| | | |
| | | Returns: |
| | | BuildResult for the main package |
| | |
| | | logger.info(f"Adding package: {package}") |
| | | |
| | | # Resolve dependencies |
| | | build_order = await self.resolver.resolve([package]) |
| | | exclude_repo = None if include_repo else self.config.repository.name |
| | | build_order = await self.resolver.resolve([package], exclude_repo=exclude_repo) |
| | | |
| | | # Filter build order: skip managed repo, download others, build AUR |
| | | final_results: list[BuildResult] = [] |
| | |
| | | repo = self.resolver.is_in_repos(pkg_name) |
| | | |
| | | if repo == self.config.repository.name: |
| | | logger.info(f"Package {pkg_name} already in managed repository, skipping") |
| | | if pkg_name == package: |
| | | return BuildResult(package=package, status=BuildStatus.SKIPPED) |
| | | continue |
| | | if include_repo: |
| | | logger.info(f"Package {pkg_name} already in managed repository, skipping") |
| | | if pkg_name == package: |
| | | return BuildResult(package=package, status=BuildStatus.SKIPPED) |
| | | continue |
| | | else: |
| | | # Treat as not in repo to force rebuild from AUR |
| | | repo = None |
| | | |
| | | if repo: |
| | | logger.info(f"Package {pkg_name} found in {repo}, downloading...") |
| | |
| | | |
| | | @cli.command() |
| | | @click.argument("packages", nargs=-1, required=True) |
| | | @click.option( |
| | | "--include-repo", |
| | | is_flag=True, |
| | | default=False, |
| | | help="Check managed repository for existing packages (skip if present)", |
| | | ) |
| | | @pass_context |
| | | def add(ctx: Context, packages: tuple[str, ...]) -> None: |
| | | def add(ctx: Context, packages: tuple[str, ...], include_repo: bool) -> None: |
| | | """Add and build new packages from the AUR.""" |
| | | config = ctx.config |
| | | |
| | |
| | | results = [] |
| | | for package in packages: |
| | | console.print(f"[bold blue]Adding package:[/] {package}") |
| | | result = await builder.add_package(package) |
| | | result = await builder.add_package(package, include_repo=include_repo) |
| | | results.append(result) |
| | | |
| | | if result.status == BuildStatus.SUCCESS: |
| | |
| | | # Remove package files |
| | | removed = 0 |
| | | for f in self.config.repository.path.glob(f"{package}-*.pkg.tar.*"): |
| | | f.unlink() |
| | | removed += 1 |
| | | if f.name.endswith(".sig"): |
| | | continue |
| | | name, _, _ = self._parse_pkg_filename(f.name) |
| | | if name == package: |
| | | f.unlink() |
| | | removed += 1 |
| | | # Also remove signature |
| | | sig = f.with_suffix(f.suffix + ".sig") |
| | | if sig.exists(): |
| | | sig.unlink() |
| | | |
| | | logger.info(f"Removed {package} ({removed} files)") |
| | | return True |
| | |
| | | # Find all package files |
| | | files = list(self.config.repository.path.glob(pattern)) |
| | | files = [f for f in files if not f.name.endswith(".sig")] |
| | | # Filter to ensure exact package name match (to avoid matching sub-packages) |
| | | files = [f for f in files if self._parse_pkg_filename(f.name)[0] == package] |
| | | |
| | | if len(files) <= keep_versions: |
| | | return 0 |
| | |
| | | dep_parsed = Dependency.parse(dep) |
| | | base_name = dep_parsed.name |
| | | |
| | | # Skip if in repos or already installed |
| | | # Skip if in repos |
| | | if self.is_in_repos(base_name): |
| | | continue |
| | | if self.is_installed(base_name): |
| | | continue |
| | | |
| | | aur_deps.append(base_name) |
| | | graph[package.name].add(base_name) |
| | |
| | | |
| | | return cycles |
| | | |
| | | async def resolve(self, package_names: list[str]) -> BuildOrder: |
| | | async def resolve(self, package_names: list[str], exclude_repo: str | None = None) -> BuildOrder: |
| | | """Resolve dependencies and determine build order. |
| | | |
| | | Args: |
| | | package_names: List of packages to resolve |
| | | exclude_repo: Optional repository name to exclude from existence checks |
| | | |
| | | Returns: |
| | | BuildOrder with packages in correct build order |
| | |
| | | # Filter out packages already in repos or installed |
| | | aur_package_names = [] |
| | | for name in package_names: |
| | | if self.is_in_repos(name): |
| | | logger.info(f"Package {name} found in repositories, skipping AUR lookup") |
| | | continue |
| | | if self.is_installed(name): |
| | | logger.info(f"Package {name} is already installed, skipping AUR lookup") |
| | | continue |
| | | repo = self.is_in_repos(name) |
| | | if repo: |
| | | if exclude_repo and repo == exclude_repo: |
| | | logger.debug(f"Package {name} found in excluded repo {repo}, treating as not in repos") |
| | | else: |
| | | logger.info(f"Package {name} found in {repo}, skipping AUR lookup") |
| | | continue |
| | | aur_package_names.append(name) |
| | | |
| | | if not aur_package_names: |
| | |
| | | resolver = DependencyResolver(mock_aur_client) |
| | | |
| | | # Mock AUR response |
| | | from datetime import datetime |
| | | pkg = Package( |
| | | name="test-pkg", |
| | | version="1.0", |
| | |
| | | votes=0, |
| | | popularity=0.0, |
| | | out_of_date=None, |
| | | first_submitted=None, |
| | | last_modified=None, |
| | | first_submitted=datetime.now(), |
| | | last_modified=datetime.now(), |
| | | package_base="test-pkg", |
| | | depends=[], |
| | | makedepends=[], |
| | | checkdepends=["check-dep"], |
| | |
| | | votes=0, |
| | | popularity=0.0, |
| | | out_of_date=None, |
| | | first_submitted=None, |
| | | last_modified=None, |
| | | first_submitted=datetime.now(), |
| | | last_modified=datetime.now(), |
| | | package_base="check-dep", |
| | | depends=[], |
| | | makedepends=[], |
| | | checkdepends=[], |
| | |
| | | assert "check-dep" in build_order.packages |
| | | assert "check-dep" in [d.name for d in build_order.aur_dependencies["test-pkg"]] |
| | | assert any(d.dep_type == DependencyType.CHECK for d in build_order.aur_dependencies["test-pkg"]) |
| | | |
| | | @pytest.mark.asyncio |
| | | async def test_resolve_with_exclude_repo(self, mock_aur_client): |
| | | """Test that resolve correctly handles exclude_repo.""" |
| | | from archrepobuild.aur import Package |
| | | |
| | | resolver = DependencyResolver(mock_aur_client) |
| | | |
| | | from datetime import datetime |
| | | # Mock AUR response |
| | | pkg = Package( |
| | | name="test-pkg", |
| | | version="1.0", |
| | | description="test", |
| | | url=None, |
| | | maintainer=None, |
| | | votes=0, |
| | | popularity=0.0, |
| | | out_of_date=None, |
| | | first_submitted=datetime.now(), |
| | | last_modified=datetime.now(), |
| | | package_base="test-pkg", |
| | | depends=[], |
| | | makedepends=[], |
| | | checkdepends=[], |
| | | ) |
| | | |
| | | mock_aur_client.get_packages.return_value = [pkg] |
| | | |
| | | # Case 1: Package in repo, not excluded -> should be skipped |
| | | with patch.object(resolver, "is_in_repos", return_value="myrepo"): |
| | | build_order = await resolver.resolve(["test-pkg"]) |
| | | assert "test-pkg" not in build_order.packages |
| | | |
| | | # Case 2: Package in repo, excluded -> should be included (AUR lookup) |
| | | with patch.object(resolver, "is_in_repos", return_value="myrepo"): |
| | | build_order = await resolver.resolve(["test-pkg"], exclude_repo="myrepo") |
| | | assert "test-pkg" in build_order.packages |
| | | |
| | | # Case 3: Package in different repo, not excluded -> should be skipped |
| | | with patch.object(resolver, "is_in_repos", return_value="otherrepo"): |
| | | build_order = await resolver.resolve(["test-pkg"], exclude_repo="myrepo") |
| | | assert "test-pkg" not in build_order.packages |