###############################################################################
import asyncio
+import difflib
import json
import logging
+import os
+import re
+import shutil
+import tempfile
import urllib.parse
from . import base
# Check
async def check(self):
+ release = None
+
# Wait until we are allowed to send an API request
async with ratelimiter:
log.info("Checking for new releases for %s" % self)
try:
if self.follow == "mainline":
- await self._follow_mainline(versions)
+ release = await self._follow_mainline(versions)
else:
raise ValueError("Cannot handle follow: %s" % self.follow)
except ReleaseExistsError as e:
log.debug("Release %s already exists" % e)
+ # Launch the build
+ if release and release.build:
+ await self.backend.builds.launch([release.build])
+
async def _fetch_versions(self):
"""
Fetches all versions for this project
# Create a build
if self.data.create_builds:
- await release._create_build()
+ await release._create_build(
+ build=self.latest_build,
+ owner=self.backend.users.pakfire,
+ )
# Return the release
return release
Deletes this release
"""
async with asyncio.TaskGroup() as tasks:
+ # Delete the repository
+ if self.repo:
+ await self.repo.delete()
+
# Delete the build
if self.build:
tasks.create_task(self.build.delete(user=user))
if self.bug_id:
return await self.backend.bugzilla.get_bug(self.bug_id)
+ # Repo
+
+ def get_repo(self):
+ if self.data.repo_id:
+ return self.backend.repos.get_by_id(self.data.repo_id)
+
+ def set_repo(self, repo):
+ if self.repo:
+ raise AttributeError("Cannot reset repo")
+
+ self._set_attribute("repo_id", repo)
+
+ repo = lazy_property(get_repo, set_repo)
+
# Build
def get_build(self):
build = lazy_property(get_build, set_build)
- async def _create_build(self):
+ async def _create_build(self, build, owner):
"""
Creates a build
"""
if self.build:
raise RuntimeError("Build already exists")
- # XXX since we cannot yet update any builds, we will simply clone the latest one
- # to test the tooling
- #print(self.monitoring.latest_build)
+ log.info("Creating build for %s from %s" % (self, build))
+
+ try:
+ # Create a new repository
+ repo = await self.backend.repos.create(
+ self.monitoring.distro, "Test Build for %s" % self, owner=owner)
+
+ # Create a new temporary space for the
+ with tempfile.TemporaryDirectory() as target:
+ # Create a new source package
+ file = await asyncio.to_thread(
+ self._update_source_package, build.pkg, target)
+
+ # Upload the file
+ upload = await self.backend.uploads.create_from_local(file)
+
+ try:
+ # Create a package
+ package = await self.backend.packages.create(upload)
+
+ # Create the build
+ build = await self.backend.builds.create(repo, package, owner=owner)
+
+ finally:
+ await upload.delete()
+
+ # If anything went wrong, then remove the repository
+ except Exception as e:
+ await repo.delete()
+
+ raise e
+
+ else:
+ # Store the objects
+ self.build = build
+ self.repo = repo
+
+ def _update_source_package(self, package, target):
+ """
+ Takes a package and recreates it with this release
+ """
+ if not package.is_source():
+ raise RuntimeError("%s is not a source package" % package)
+
+ # Create temporary directory to extract the package to
+ with tempfile.TemporaryDirectory() as tmp:
+ # Path to downloaded files
+ files = os.path.join(tmp, "files")
+
+ # Path to the makefile
+ makefile = os.path.join(tmp, "%s.nm" % package.name)
+
+ # Create a Pakfire instance from this distribution
+ with self.monitoring.distro.pakfire() as p:
+ # Open the archive
+ archive = p.open(package.path)
+
+ # Extract the archive into the temporary space
+ archive.extract(path=tmp)
+
+ # XXX directories are being created with the wrong permissions
+ os.system("chmod a+x -R %s" % tmp)
+
+ # Remove any downloaded files
+ shutil.rmtree(files)
+
+ # Update the makefile
+ diff = self._update_makefile(makefile)
+
+ # Log the diff
+ log.info("Generated diff:\n%s" % diff)
+
+ # Store the diff
+ self._set_attribute("diff", diff)
+
+ # Generate a new source package
+ return p.dist(makefile, target)
+
+ def _update_makefile(self, path):
+ """
+ Reads the makefile in path and updates it with the newer version
+ returning a diff between the two.
+ """
+ filename = os.path.basename(path)
+
+ # Read the makefile
+ with open(path, "r") as f:
+ orig = f.readlines()
+
+ # Replace the version & release
+ updated = self._update_makefile_version(orig)
+
+ # Write the new file
+ with open(path, "w") as f:
+ f.writelines(updated)
+
+ # Generate a diff
+ return "".join(
+ difflib.unified_diff(orig, updated, fromfile=filename, tofile=filename),
+ )
+
+ def _update_makefile_version(self, lines, release=1):
+ result = []
+
+ # Walk through the file line by line and replace everything that
+ # starts with version or release.
+ for line in lines:
+ if line and not line.startswith("#"):
+ # Replace version
+ m = re.match(r"^(version\s*=)\s*(.*)$", line)
+ if m:
+ line = "%s %s\n" % (m.group(1), self.version)
+
+ # Replace release
+ m = re.match(r"^(release\s*=)\s*(.*)$", line)
+ if m:
+ line = "%s %s\n" % (m.group(1), release)
+
+ result.append(line)
- # XXX does this need an owner?
- #self.build = await self.monitoring.latest_build.create(
- # repo=None, package=self.monitoring.latest_build.package)
+ return result