# Part of the A-A-P recipe executive: Add dependencies for a port recipe # Copyright (C) 2002-2003 Stichting NLnet Labs # Permission to copy and use this file is specified in the file COPYING. # If this file is missing you can find it here: http://www.a-a-p.org/COPYING # # This file defines the code used for a port recipe. # import os.path import string from Action import action_run from Depend import Depend from Dictlist import str2dictlist, dictlist2str from Message import * from Process import recipe_error from RecPos import RecPos from Sign import check_md5 from Util import * from VersCont import repl_file_name, handle_nodelist from Work import getwork import Global # List of "*depend" target names that "dependcheck" needs to use. depend_list = [] # Last target that will be used. last_target = None # Whether last_target was used, no further dependencies to be checked. last_target_found = 0 cvs_done_file = "done/cvs-yes" # file created when CVS was used. cvs_notdone_file = "done/cvs-no" # file created when archives were used. def check_port_dep(work, name): n = work.find_node(name) if n and n.get_dependencies(): recipe_error([], _('"%s" target already defined while $PORTNAME is present') % name) def add_port_dep(work, rpstack, item, previtem): """Add one of the port dependencies "item". It will depend on "previtem".""" # # Note: all build commands have a hard coded minimal indent of two spaces. # # Don't do anything when the "done/" file exists. if item.done_check and os.path.exists("done/" + item.done_check): cmds = '' else: # Update "pre-name" when it was defined. n = work.find_node("pre-" + item.name) if n: n.set_attributes({"virtual": 1}) cmds = " :update pre-" + item.name + '\n' else: cmds = '' # Update "do-name" when it was defined, use the default commands # otherwise. n = work.find_node("do-" + item.name) if n: n.set_attributes({"virtual": 1}) cmds = cmds + (' :update do-%s\n' % item.name) else: cmds = cmds + (' @port_%s(globals())\n' % item.name) # Update "post-name" when it was defined. n = work.find_node("post-" + item.name) if n: n.set_attributes({"virtual": 1}) cmds = cmds + " :update post-" + item.name + '\n' # Create the "done" file, except for "install" and "*depend". if item.done_create: cmds = cmds + " :mkdir {force} done\n" cmds = cmds + " :touch {force} done/" + item.name + '\n' # For "install" handle the runtime dependencies after installing the # package, reduces cyclic dependencies. Then run the post-install # tests. # TODO: if this fails, uninstall the package? if item.name == "install": cmds = cmds + " :update rundepend\n" cmds = cmds + " :update installtest\n" # # Add "*depend" items to the list of dependencies to be satisfied. # This stops at the last target that will be build. # global last_target, last_target_found if item.name[-6:] == "depend" and not last_target_found: global depend_list depend_list.append(item.name) if item.name == last_target: last_target_found = 1 targets = [{"name": item.name, "virtual" : 1}] if previtem: sources = [{"name": previtem.name}] else: sources = [] work.add_dependency(rpstack, Depend(targets, {}, sources, work, rpstack, cmds, recdict = work.recdict)) class port_step: def __init__(self, name, done_check, done_create): self.name = name self.done_check = done_check # "done" file to check for existence self.done_create = done_create # flag: create "done" file if success def add_port_defaults(work): """Add the dependencies used for a port recipe.""" # The list of virtual targets to be build for a port. deplist = [port_step("dependcheck", "", 0), port_step("fetchdepend", "checksum", 0), port_step("fetch", "fetch", 1), port_step("checksum", "checksum", 1), port_step("extractdepend", "patch", 0), port_step("extract", "extract", 1), port_step("patch", "patch", 1), port_step("builddepend", "build", 0), port_step("config", "config", 1), port_step("build", "build", 1), port_step("testdepend", "test", 0), port_step("test", "test", 1), port_step("package", "package", 1), port_step("install", "", 0)] # Check if the user didn't accidentally overrule one of the targets. for item in deplist: check_port_dep(work, item.name) check_port_dep(work, "rundepend") check_port_dep(work, "installtest") for name in ["PORTVERSION", "PORTCOMMENT", "PORTDESCR"]: if not get_var_val_int(work.recdict, name): if work.recdict["_no"].has_key(name): recipe_error([], _('Empty variable "%s"') % name) else: recipe_error([], _('Missing variable "%s"') % name) msg_depend(work.recdict, _('Adding dependencies for port recipe')) # # Add the "all" target used for a port recipe. # This builds the port but doesn't test or install it. # Testing would be nice, but requires "rundepend" to be done. # rpstack = [RecPos("Default port target")] work.add_dependency(rpstack, Depend([{"name": "all"}], {}, [{"name" : "build"}], work, rpstack, '', recdict = work.recdict)) # # Find which target is the last to be done. Allows checking only the # dependencies that are actually used. # global last_target for item in deplist: if item.name in Global.cmd_args.targets: last_target = item.name # # Add a dependency for each target. It depends on the previous one. # previtem = None for item in deplist: add_port_dep(work, rpstack, item, previtem) previtem = item # "rundepend" and "installtest" are updated from inside "install", need to # create its dependency separately. add_port_dep(work, rpstack, port_step("rundepend", "", 0), None) add_port_dep(work, rpstack, port_step("installtest", "", 0), None) # # Add dependencies that are used individually. # indeplist = [port_step("clean", "", 0), port_step("distclean", "", 0), port_step("uninstall", "", 0), port_step("makesum", "", 0), port_step("srcpackage", "", 0)] for item in indeplist: add_port_dep(work, rpstack, item, None) # # Set default values for variables. # rd = work.recdict if use_cvs(rd): # Set $WRKSRC to the value used for CVS. adir = get_var_val_int(rd, "CVSWRKSRC") if adir: msg_extra(rd, _("Using $CVSWRKSRC for $WRKSRC: %s") % adir) rd["WRKSRC"] = adir else: modules = str2dictlist([], get_var_val_int(rd, "CVSMODULES")) s = modules[0]["name"] msg_extra(rd, _("Using first CVS module for $WRKSRC: %s") % s) rd["WRKSRC"] = s rd["CVSWRKSRC"] = s else: if not get_var_val_int(rd, "WRKSRC"): s = (get_var_val_int(rd, "PORTNAME") + "-" + get_var_val_int(rd, "PORTVERSION")) rd["WRKSRC"] = s msg_extra(rd, _("Using $PORTNAME for $WRKSRC: %s") % s) def port_clean(recdict, dist = 0): """Implementation of the "clean" target. Also used for "distclean", "dist" is non-zero then.""" alist = ["done", get_var_val_int(recdict, "WRKDIR"), get_var_val_int(recdict, "PKGDIR"), "pkg-plist", "pkg-comment", "pkg-descr"] if dist: alist.extend([get_var_val_int(recdict, "DISTDIR"), get_var_val_int(recdict, "PATCHDISTDIR"), get_pkgname(recdict) + ".tgz", Global.aap_dirname]) for adir in alist: if os.path.exists(adir): try: deltree(adir) except StandardError, e: recipe_error([], (_('Cannot delete "%s": ') % adir) + str(e)) def port_distclean(recdict): """Implementation of the "distclean" target.""" port_clean(recdict, 1) def port_uninstall(recdict): """Implementation of the "uninstall" target.""" msg_info(recdict, _("TODO: uninstall")) def port_makesum(recdict): """Implementation of the "makesum" target.""" # Get the recipe name. work = getwork(recdict) if not work.top_recipe: # cannot happen? recipe_error([], _("No recipe specified to makesum for")) # # Get the total list of files and compute the checksum for each. # lines = [] for name, adir in [("DISTFILES", "DISTDIR"), ("PATCHFILES", "PATCHDISTDIR")]: files = [] # Get the normal list of files and then the CVS list (if defined). for varname in [name, "CVS" + name]: f = get_var_val_int(recdict, varname) if f: for i in str2dictlist([], f): n = os.path.basename(i["name"]) if not n in files: files.append(n) # Make a recipe line for for each file, containing the md5 checksum. # TODO: use another kind of checksum when specified. for f in files: n = os.path.join(get_var_val_int(recdict, adir), f) if not os.path.exists(n): recipe_error([], _('File does not exists: "%s"') % n) md5sum = check_md5(recdict, n) if md5sum == "unknown": recipe_error([], _('Cannot compute checksum for "%s"') % n) lines.append("\t:checksum $%s/%s {md5 = %s}\n" % (adir, f, md5sum)) # # Replace or append the lines in/to the recipe # startline = '#>>> automatically inserted by "aap makesum" <<<\n' endline = '#>>> end <<<\n' # Open the original recipe file. try: fr = open(work.top_recipe) except StandardError, e: recipe_error([], (_('Cannot open recipe file "%s": ') % work.top_recipe) + str(e)) # Create a new file for the updated recipe. i = 1 while 1: temp = work.top_recipe + str(i) if not os.path.exists(temp): break i = i + 1 try: fw = open(temp, "w") except StandardError, e: recipe_error([], (_('Cannot create temp file "%s": ') % temp) + str(e)) def write_checksum_lines(fw, lines, endl): fw.write("do-checksum:\n") if lines: fw.writelines(lines) else: fw.write("\t@pass\n") fw.write(endl) # # Copy original to updated recipe, replacing the makesum block. # try: added = 0 while 1: line = fr.readline() if not line: break fw.write(line) if line == startline: if added: recipe_error([], _("Duplicate makesum start marker")) added = 1 write_checksum_lines(fw, lines, endline) while 1: line = fr.readline() if not line: recipe_error([], _("Missing makesum end marker")) if line == endline: break if not added: # makesum block not found, add it at the end. fw.write(startline) write_checksum_lines(fw, lines, endline) fr.close() fw.close() except (StandardError, UserError), e: try: fw.close() except: pass os.remove(temp) recipe_error([], _('Error while copying recipe file: ') + str(e)) # # Rename original to backup and update to original recipe. # Most of the work is giving appropriate message when something goes wrong. # TODO: preserve protection bits. # bak = work.top_recipe + "~" if os.path.exists(bak): try: os.remove(bak) except StandardError, e: try_delete(temp) recipe_error([], (_('Cannot delete backup recipe "%s": ') % bak) + str(e)) try: os.rename(work.top_recipe, bak) except StandardError, e: try_delete(temp) recipe_error([], (_('Cannot rename recipe "%s" to "%s": ') % (work.top_recipe, bak)) + str(e)) try: os.rename(temp, work.top_recipe) except StandardError, e: recipe_error([], (_('Cannot rename recipe to "%s": ') % work.top_recipe) + str(e)) # renaming failed, try putting the original recipe back. try: os.rename(bak, work.top_recipe) except StandardError, e: recipe_error([], (_('Cannot rename recipe! It is now called "%s": ') % bak) + str(e)) # If the rename worked the remove fails, that's normal. try_delete(temp) def port_srcpackage(recdict): """Implementation of the "srcpackage" target.""" msg_info(recdict, _("TODO: srcpackage")) # Cached list of packages. # TODO: Needs to be flushed if a package is installed! all_packages = None def get_installed(recdict): """Return a list of all installed packages.""" # TODO: this currently only works for systems with pkg_info. # TODO: pkg_info is slow, because it obtains descriptions. Can we just get # the contents of /var/db/pkg? global all_packages if all_packages is None: ok, text = redir_system_int(recdict, "pkg_info -aI", use_tee = 0) if not ok: msg_error(recdict, _("Could not obtain list of installed packages")) else: # Get the package name from the line "name-9.9 description". all_packages = [] lines = string.split(text, '\n') for l in lines: items = string.split(l, None, 1) if items: all_packages.append(items[0]) return all_packages def depend_matches(plist, pat): """Select the items from list "plist" that match pattern "pat".""" import fnmatch res = [] for i in plist: # TODO: don't match "vimxx" when looking for "vim", do match "vim-1.1". if fnmatch.fnmatchcase(i, pat + "*"): res.append(i) return res def depend_item_match(name, op, pat): """Return non-zero if package "name" matches with operation "op" and pattern "pat".""" # Find the part of "name" until the version number. ns = 0 while ns < len(name) - 1: ns = ns + 1 if name[ns - 1] == '-' and name[ns] in string.digits: break ps = 0 while ns < len(name) and ps < len(pat): # Compare each dot separated part. # First get the digits. ne = ns while ne < len(name) and name[ne] in string.digits: ne = ne + 1 name_part = name[ns:ne] pe = ps while pe < len(pat) and pat[pe] in string.digits: pe = pe + 1 pat_part = pat[ps:pe] # Fill with zeros so that 9 is smaller than 10. while len(name_part) < len(pat_part): name_part = '0' + name_part while len(name_part) > len(pat_part): pat_part = '0' + pat_part # Add text until a dot. while ne < len(name) and name[ne] != ".": name_part = name_part + name[ne] ne = ne + 1 while pe < len(pat) and pat[pe] != ".": pat_part = pat_part + pat[pe] pe = pe + 1 # Compare. if op[0] == "<": if name_part > pat_part: return 0 if name_part < pat_part: return 1 elif op[0] == ">": if name_part < pat_part: return 0 if name_part > pat_part: return 1 # Advance to the next part, skip over the dot. if ne < len(name): ne = ne + 1 ns = ne if pe < len(pat): pe = pe + 1 ps = pe if op == "<" and name[ns:] >= pat[ps:]: return 0 if op == ">" and name[ns:] <= pat[ps:]: return 0 return 1 def part_end(name, depends, idx): """Find the end of the next part in a depends item, up to the next "<", "!", etc. Return the index of the following char.""" e = idx while (e < len(depends) and not is_white(depends[e]) and not depends[e] in ">2.0<3.0): remove matching items # Call ourselves recursively! idx, notlist = part_remove(name, depends, idx + 1, matching[:], pname) if idx >= len(depends) or depends[idx] != ')': recipe_error([], _("Missing ) in %s") % name) idx = idx + 1 braces = 1 elif depends[idx] == '>': idx = idx + 1 if idx < len(depends) and depends[idx] == '=': idx = idx + 1 next_op = ">=" else: next_op = ">" elif depends[idx] == '<': idx = idx + 1 if idx < len(depends) and depends[idx] == '=': idx = idx + 1 next_op = "<=" else: next_op = "<" if not braces: # Isolate the one part: package name and optional version spec. e = part_end(name, depends, idx) pat = depends[idx:e] idx = e # For "!version" find the list items that are to be excluded. if next_op == '!': # Find the part of "pname" until the version number. i = 0 while i < len(pname) - 1: if pname[i] == '-' and pname[i + 1] in string.digits + '*[': break i = i + 1 patlead = pname[:i + 1] if patlead[-1] != '-': patlead = patlead + '-' notlist = depend_matches(matching, patlead + pat) # Go over all currently matching items, deleting the ones that # don't meet the condition. i = 0 while i < len(matching): if next_op == '!': match = not matching[i] in notlist else: match = depend_item_match(matching[i], next_op, pat) if match: i = i + 1 else: del matching[i] return idx, matching def depend_item(recdict, name, depends, idx): """Handle an item in a dependency spec. Return the index of the following character and a list of missing items""" # We start with all possible packages and remove the ones not matching. # First part: use name as a pattern and use all matches. e = part_end(name, depends, idx) pname = depends[idx:e] matching = depend_matches(get_installed(recdict), pname) # Following parts: Remove matches according to each part. idx, matching = part_remove(name, depends, e, matching, pname) if matching: missing = [] else: # TODO: use first version number mentioned. missing = [pname] return skip_white(depends, idx), missing def depend_top(recdict, name, depends, idx): """Handle a list of items in depends[idx:] with an "and" or "or" relation. Stop at the end of "depends" or when encountering an unmatched ")". Returns the index of the end or ")" and a list of missing packages.""" missing = [] had_op = None while idx < len(depends) and depends[idx] != ')': if depends[idx] == '(': # Call ourselves recursively to handle items in parenthesis. idx, res = depend_top(recdict, name, depends, skip_white(depends,idx + 1)) if idx >= len(depends) or depends[idx] != ')': recipe_error([], _('Missing ")" in %s') % name) idx = idx + 1 else: idx, res = depend_item(recdict, name, depends, idx) # Combine with previous results: # AND means adding more missing items. # OR means clearing missing items when there are none found now. # Otherwise it's the first item, set "missing". if had_op == "and": missing.extend(res) elif had_op == "or": if not res: missing = [] else: missing = res # Check for a following item: # "|" means an OR operation # end of string means it's done # something else means AND operation idx = skip_white(depends, idx) if idx < len(depends) and depends[idx] != ')': if depends[idx] == '|': new_op = "or" idx = skip_white(depends, idx + 1) if idx >= len(depends): recipe_error([], _("Trailing '|' in %s") % name) if depends[idx] == ')': recipe_error([], _("'|' before ')' in %s") % name) if depends[idx] == '|': recipe_error([], _("double '|' in %s") % name) else: new_op = "and" if had_op and had_op != new_op: recipe_error([], _("Illegal combination of AND and OR in %s") % name) had_op = new_op return idx, missing def depend_do(recdict, name, check_only): """Handle the "name" dependencies. When "check_only" is non-zero only check if the items can be fulfilled, don't actually install them.""" if get_var_val_int(recdict, "AUTODEPEND") != "no": varname = "DEPEND_" + string.upper(name) depends = get_var_val_int(recdict, varname) if not depends and (name == "run" or name == "build"): varname = "DEPENDS" depends = get_var_val_int(recdict, varname) if not depends: return # Figure out which desired modules are not yet installed. idx, missing = depend_top(recdict, varname, depends, skip_white(depends, 0)) if idx < len(depends): recipe_error([], _('Unmatched ")" in %s') % varname) if not missing: msg_extra(recdict, _("All %s dependencies satisfied") % name) elif check_only: msg_extra(recdict, 'Would check %s dependencies %s now' % (name, str(missing))) else: msg_extra(recdict, 'Would install %s dependencies %s now' % (name, str(missing))) def port_dependcheck(recdict): """Check if required items are present or can be installed. If not, exit with an error.""" for n in depend_list: if ((n != "testdepend" or get_var_val_int(recdict, "SKIPTEST") != "yes") and (n != "rundepend" or get_var_val_int(recdict, "SKIPRUNTIME") != "yes")): depend_do(recdict, n[:-6], 1) def use_cvs(recdict): """Return non-zero when CVS is to be used to obtain files.""" # When CVS was already used we need to use it again. if os.path.exists(cvs_done_file): return 1 # When archives were unpacked we don't use CVS. if os.path.exists(cvs_notdone_file): return 0 return (get_var_val_int(recdict, "CVSMODULES") and get_var_val_int(recdict, "CVS") != "no") def port_fetchdepend(recdict): """Obtain required items for building.""" depend_do(recdict, "fetch", 0) def port_fetch(recdict): """Obtain the files for the port.""" work = getwork(recdict) if use_cvs(recdict): # Obtain stuff through CVS. # TODO: support a list of cvsroots cwd = os.getcwd() adir = get_var_val_int(recdict, "WRKDIR") if adir: # Do this in the $WRKDIR directory. assert_dir([], recdict, adir) goto_dir(recdict, adir) try: cvsroot = get_var_val_int(recdict, "CVSROOT") modules = str2dictlist([], get_var_val_int(recdict, "CVSMODULES")) # Get each module from CVS. for f in modules: if f.has_key("cvsroot"): root = f["cvsroot"] else: root = cvsroot n = work.get_node(f["name"], 0, f) n.set_attributes({"fetch" : "cvs://" + root}) l = handle_nodelist([], recdict, [n], 1, "fetch", ["fetch"]) if l: recipe_error([], _('CVS checkout of %s failed') % f["name"]) finally: goto_dir(recdict, cwd) did_use_cvs = cvs_done_file dfs = get_var_val_int(recdict, "CVSDISTFILES") pfs = get_var_val_int(recdict, "CVSPATCHFILES") else: did_use_cvs = cvs_notdone_file dfs = get_var_val_int(recdict, "DISTFILES") if not dfs: msg_info(recdict, _("DISTFILES not set, no files fetched")) pfs = get_var_val_int(recdict, "PATCHFILES") # Check this first, to avoid getting an error after spending a lot of time # downloading the distfiles. if pfs and not get_var_val_int(recdict, "PATCH_SITES"): recipe_error([], _('Patch files defined but PATCH_SITES not defined')) # remember which files need to be extracted if not get_var_val_int(recdict, "EXTRACTFILES"): recdict["EXTRACTFILES"] = dfs # Obtain files: archives and patches for dl, sites, adir in [(str2dictlist([], dfs), "MASTER_SITES", "DISTDIR"), (str2dictlist([], pfs), "PATCH_SITES", "PATCHDISTDIR")]: # For each item in $MASTER_SITES or $PATCH_SITES append "/%file%", so # that it can be used as a fetch attribute. msl = str2dictlist([], get_var_val_int(recdict, sites)) for i in msl: i["name"] = os.path.join(i["name"], "%file%") master_fetch = dictlist2str(msl, Expand(0, Expand.quote_aap)) destdir = get_var_val_int(recdict, adir) for f in dl: # Download each file into $DISTDIR or $PATCHDISTDIR. # Let the "distdir" attribute overrule the default directory. fn = os.path.basename(f["name"]) if f.has_key("distdir"): fname = os.path.join(f["distdir"], fn) else: fname = os.path.join(destdir, fn) # Skip fetching if the file already exists. if os.path.exists(fname): msg_extra(recdict, _('file already exists: "%s"') % fname) continue # Make sure the destination directory exists. assert_dir([], recdict, os.path.dirname(fname)) # Make the fetch attribute include the full path of the file to # download. n = work.get_node(fname, 0, f) if f.has_key("fetch"): rf = f["fetch"] else: rf = master_fetch # replace %file% with the file name rf = repl_file_name(rf, f["name"]) n.set_attributes({"fetch" : rf}) l = handle_nodelist([], recdict, [n], 1, "fetch", ["fetch"]) if l: recipe_error([], _('Obtaining "%s" failed') % f["name"]) # When checkout from CVS was successful, remember that CVS is to be # used until "aap distclean" is done. try: from Commands import touch_file aap_checkdir([], recdict, did_use_cvs) touch_file(did_use_cvs, 0644); except StandardError, e: recipe_error([], (_('Cannot create "%s": ') % did_use_cvs) + str(e)) def port_checksum(recdict): """Check the checksums of obtained files for the port.""" msg_extra(recdict, 'No do-checksum target defined; checking checksums skipped') def port_extractdepend(recdict): """Obtain required items for building.""" depend_do(recdict, "extract", 0) def port_extract(recdict): """Obtain the files for the port.""" l = get_var_val_int(recdict, "EXTRACT_ONLY") if l: pass elif use_cvs(recdict): l = get_var_val_int(recdict, "CVSDISTFILES") else: l = get_var_val_int(recdict, "DISTFILES") if not l: return archlist = str2dictlist([], l) # Change the names to be in $DISTDIR and make them absolute (changing # directory below). distdir = os.path.abspath(get_var_val_int(recdict, "DISTDIR")) for x in archlist: fn = os.path.basename(x["name"]) if x.has_key("distdir"): x["name"] = os.path.abspath(os.path.join(x["distdir"], fn)) else: x["name"] = os.path.join(distdir, fn) # extract each file wrkdir = os.path.abspath(get_var_val_int(recdict, "WRKDIR")) cwd = os.getcwd() try: for f in archlist: # change to "extractdir" # Make path absolute now, before changing directories. if f.has_key("extractdir"): adir = os.path.join(wrkdir, f["extractdir"]) else: adir = wrkdir assert_dir([], recdict, adir) goto_dir(recdict, adir) # Invoke the extract action. msg = action_run(recdict, [{"name" : "extract"}, f]) if msg: recipe_error([], msg) finally: # return from $WRKDIR goto_dir(recdict, cwd) def port_patch(recdict): """Apply the patch files.""" if use_cvs(recdict): l = get_var_val_int(recdict, "CVSPATCHFILES") else: l = get_var_val_int(recdict, "PATCHFILES") if not l: return patchlist = str2dictlist([], l) # Change the names to be in $PATCHDISTDIR and make them absolute (changing # directory below). patchdistdir = os.path.abspath(get_var_val_int(recdict, "PATCHDISTDIR")) for x in patchlist: fn = os.path.basename(x["name"]) if x.has_key("distdir"): x["name"] = os.path.abspath(os.path.join(x["distdir"], fn)) else: x["name"] = os.path.join(patchdistdir, fn) # Handle each patch file separately, it may have attributes for a # different patch command or directory. wrkdir = os.path.abspath(get_var_val_int(recdict, "WRKDIR")) cwd = os.getcwd() try: for fd in patchlist: # Decide what command to use for patching. if fd.get("patchcmd"): cmd = fd["patchcmd"] else: cmd = get_var_val_int(recdict, "PATCHCMD") if not cmd: cmd = "patch -p -f -s < " # TODO: shell quoting i = string.find(cmd, "%s") if i >= 0: cmd = cmd[:i] + fd["name"] + cmd[i + 2:] else: cmd = cmd + fd["name"] # Go to the directory for patching. if fd.get("patchdir"): adir = fd["patchdir"] else: adir = get_var_val_int(recdict, "PATCHDIR") if not adir: adir = get_var_val_int(recdict, "WRKSRC") adir = os.path.join(wrkdir, adir) goto_dir(recdict, adir) # Execute the patch command. n = logged_system(recdict, cmd) if n: recipe_error([], _("Shell returned %d when patching:\n%s") % (n, cmd)) finally: goto_dir(recdict, cwd) def port_builddepend(recdict): """Obtain required items for building.""" depend_do(recdict, "build", 0) def port_config(recdict): """Configure the port.""" cmd = get_var_val_int(recdict, "CONFIGURECMD") if cmd: port_exe_cmd(recdict, cmd, "BUILDDIR") else: msg_extra(recdict, 'No CONFIGURECMD specified') def port_build(recdict): """Build the port.""" # Decide what command to use for building. cmd = get_var_val_int(recdict, "BUILDCMD") if not cmd: cmd = "aap" port_exe_cmd(recdict, cmd, "BUILDDIR") def port_exe_cmd(recdict, cmd, dirname): """Execute "cmd" in directory possibly specified with "dirname".""" # Go to the build/test directory. cwd = os.getcwd() adir = get_var_val_int(recdict, dirname) if not adir: adir = get_var_val_int(recdict, "WRKSRC") adir = os.path.join(get_var_val_int(recdict, "WRKDIR"), adir) try: goto_dir(recdict, adir) except StandardError, e: recipe_error([], (_('Cannot change to directory "%s": ') % adir) + str(e)) try: # Execute the build command. # When it's the default, avoid starting another instance of ourselves. # It would redirect (and echo) the output twice. if ((len(cmd) == 3 and cmd == "aap") or (len(cmd) >= 4 and cmd[:4] == "aap ")): from Commands import aap_execute from Work import setrpstack, getrpstack # Don't copy our recdict, it would cause things like "PORTNAME" to # be defined, which isn't appropriate for the build recipe. rd = {} setrpstack(rd, getrpstack(recdict)) rd["_work"] = recdict["_work"] aap_execute(0, rd, "main.aap" + cmd[3:]) else: n = logged_system(recdict, cmd) if n: recipe_error([], _("Shell returned %d when executing:\n%s") % (n, cmd)) finally: goto_dir(recdict, cwd) def port_testdepend(recdict): """Obtain required items for testing the port.""" if get_var_val_int(recdict, "SKIPTEST") != "yes": depend_do(recdict, "test", 0) def port_test(recdict): """Do the tests.""" # Skip this when not testing if get_var_val_int(recdict, "SKIPTEST") != "yes": # Decide what command to use for testing. cmd = get_var_val_int(recdict, "TESTCMD") if not cmd: cmd = "aap test" # Execute the command in the test directory. port_exe_cmd(recdict, cmd, "TESTDIR") def _write_var2file(recdict, varname, fname): # Skip when not actually building. if skip_commands(): msg_info(recdict, _('skip writing to "%s"') % fname) return try: f = open(fname, "w") except IOError, e: recipe_error([], (_('Cannot open "%s" for writing') % fname) + str(e)) try: s = get_var_val_int(recdict, varname) f.write(s) if s[-1] != '\n': f.write('\n') f.close() except IOError, e: recipe_error([], (_('Cannot write to "%s"') % fname) + str(e)) def get_pkgname(recdict): n = (get_var_val_int(recdict, "PORTNAME") + "-" + get_var_val_int(recdict, "PORTVERSION")) rv = get_var_val_int(recdict, "PORTREVISION") if rv: n = n + '_' + rv return n def port_package(recdict): """Turn the port into a package.""" # This only works for "pkg_create" for now. from RecPython import program_path if not program_path("pkg_create"): recipe_error([], _('"pkg_create" command not found')) # Copy or install the files from "work" to the "pack" directory. pkgdir = os.path.abspath(get_var_val_int(recdict, "PKGDIR")) # Make sure the pack directory exists and is empty. if os.path.exists(pkgdir): try: deltree(pkgdir) except StandardError, e: recipe_error([], (_('Cannot make "%s" directory empty: ') % pkgdir) + str(e)) assert_dir([], recdict, pkgdir) # TODO if 0 and get_var_val_int(recdict, "PACKFILES"): # Copy files mentioned in "PACKFILES" to $PKGDIR. prefix = '' else: # Execute the command in the test directory. cmd = get_var_val_int(recdict, "INSTALLCMD") if not cmd: # TODO: quoting cmd = "aap install DESTDIR=%s" % pkgdir port_exe_cmd(recdict, cmd, "INSTALLDIR") prefix = get_var_val_int(recdict, "PREFIX") if not prefix: prefix = "/usr/local" # Remove a leading slash, join() would do the wrong thing. if prefix and prefix[0] == '/': prefix = prefix[1:] # Write packing list in pkg-plist filesdir = os.path.join(pkgdir, prefix) try: filelist = dir_contents(filesdir, 1, 0) except StandardError, e: recipe_error([], (_('Could not list files in "%s": ') % filesdir) + str(e)) if skip_commands(): msg_info(recdict, _('skip writing to pkg-plist')) else: try: f = open("pkg-plist", "w") except StandardError, e: recipe_error([], _('Could not open pkg-plist for writing: ') + str(e)) try: f.write("@cwd /usr/local\n") f.write("@srcdir %s\n" % filesdir) f.writelines(map(lambda x: x + '\n', filelist)) f.close() except StandardError, e: recipe_error([], _('Could not write to pkg-plist') + str(e)) # Write description in pkg-descr _write_var2file(recdict, "PORTDESCR", "pkg-descr") # Write comment in pkg-comment _write_var2file(recdict, "PORTCOMMENT", "pkg-comment") # Create the package pkgname = get_pkgname(recdict) cmd = ("pkg_create -f pkg-plist -c pkg-comment -d pkg-descr %s" % pkgname) n = logged_system(recdict, cmd) if n: recipe_error([], _("Shell returned %d when packaging:\n%s") % (n, cmd)) sus_out = None sus_in = None sus_err = None def port_install(recdict): """Install the package.""" # Open a shell to install the package. # TODO: skip this if user is root or installing in user home dir. open_sushell(recdict) pkgname = get_pkgname(recdict) + ".tgz" sus_in.write('I' + pkgname + '\n') sus_in.flush() while 1: m = sus_out.readline() if not m: recipe_error([], 'Installing %s aborted' % pkgname) if m[:10] == "PkgInstall": msg_extra(recdict, m) if m[11:13] != "OK": recipe_error([], 'Installing %s failed' % pkgname) break msg_info(recdict, m) def open_sushell(recdict): """Open a connection to a su-shell to install packages under root permissions.""" global sus_out, sus_in, sus_err import popenerr import select if sus_out is None and sus_in is None: # Run the shell and get the input and output files. msg_info(recdict, _("Starting a separate shell to run pkg_add.")) msg_info(recdict, _("Please enter the root password:")) sus_out, sus_in, sus_err = popenerr.popen3('su root -c %s' % os.path.join(Global.aap_rootdir, "PkgInstall.py")) # Loop until logging in has been done. if sus_err: fdlist = [sus_out, sus_err] else: fdlist = [sus_out] try: while 1: # Read both from stdout and stderr until PkgInstall has been # started. "su" may write to stderr. inp, outp, exc = select.select([], fdlist, []) m = outp[0].readline() if not m: recipe_error([], _('Starting super-user shell failed')) if m[:16] == "PkgInstall ready": break msg_info(recdict, m) except StandardError, e: recipe_error([], _('Error while installing package: ') + str(e)) if sus_out is None or sus_in is None: recipe_error([], _('Failed to start a shell to install packages')) def close_sushell(recdict): """Close the su-shell.""" global sus_out, sus_in, sus_err if sus_in: try: sus_in.write('Q\n') sus_in.flush() sus_out.close() sus_out = None sus_in.close() sus_in = None try: sus_err.close() # see popenerr.py why this is necessary except: pass sus_err = None except: msg_info(recdict, 'could not close shell for installing packages') else: msg_extra(recdict, 'closed shell for installing packages') def port_rundepend(recdict): """Obtain required items for running the port.""" # TODO: add the code to do the work. if get_var_val_int(recdict, "SKIPRUNTIME") != "yes": depend_do(recdict, "run", 0) def port_installtest(recdict): """Post-install test: defaults to nothing.""" msg_extra(recdict, _("Default installtest: do nothing")) # vim: set sw=4 et sts=4 tw=79 fo+=l: