# Part of the A-A-P recipe executive: Add default rules and dependencies
# 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
import string
import os.path
from Action import find_primary_action
from Dictlist import varname2dictlist,dictlist2str
from RecPos import RecPos,rpcopy
from Util import *
from Depend import Depend
from Message import *
from Process import Process,recipe_error
from ParsePos import ParsePos
from Scope import get_build_recdict
from DoBuild import find_autodep_items
from RecPython import srcitem2obj,topdir
def doadddef(work, recdict, toplevel):
"""
Add default dependencies, depending on what was defined in the
recipe(s) read.
"""
# Add a dependency from $SOURCE and $TARGET if appropriate.
add_source_target(work, recdict)
# Add a comment to the useful targets we add here.
caddlist = []
for n, c in [("install", _("install files to directory $PREFIX")),
("uninstall", _("delete installed files from directory $PREFIX")),
("clean", _("delete all generated files that are not distributed")),
("cleanmore", _("delete all generated files")),
("cleanALL", _("delete all generated files, AAPDIR and build-* directories"))]:
if not work.find_node(n):
caddlist.append((n, c))
# Add "clean" and "cleanmore" target if appropriate.
add_clean(work, recdict)
# At the toplevel we add more things.
if toplevel:
# Add the stuff for ports if $PORTNAME is defined.
if recdict.get("PORTNAME"):
from Port import add_port_defaults
add_port_defaults(work)
# Add "cleanALL" when not defined.
add_optional_target(work, recdict, "cleanALL", [ "clean" ],
":tree . {dirname = AAPDIR}\n"
" :del {f}{r} $name\n"
":tree . {dirname = build-.*}\n"
" :del {f}{r} $name\n")
# Add "install" and friends when not defined.
# Also does the # "uninstall" variant.
add_install_target(work, recdict, "install",
[ "install-platform",
"install-shared" ],
"@if has_target('install-local'):\n"
" :update install-local\n")
add_install_target(work, recdict, "install-platform",
[ "install-exec",
"install-sbin",
"install-lib",
"install-dll",
"install-ltlib",
"install-conf" ],
"@if has_target('install-platform-local'):\n"
" :update install-platform-local\n")
add_install_target(work, recdict, "install-shared",
["install-data",
"install-man",
"install-info",
"install-include"],
"@if has_target('install-shared-local'):\n"
" :update install-shared-local\n")
# These targets are all alike.
for t in ["exec", "sbin", "lib", "dll", "ltlib", "conf",
"data", "man", "info", "include"]:
tup = string.upper(t)
add_install_target(work, recdict, "install-%s" % t, [],
"@if _top.get('INSTALL_%s'):\n"
" :update $_top.INSTALL_%s\n"
" :do install%s $_top.INSTALL_%s\n" % (tup, tup, t, tup))
# Add the comments after adding the dependencies.
for n, c in caddlist:
work.get_node(n, 1).set_attributes({"comment" : c})
def add_source_target(work, recdict):
"""
Add build dependencies for $SOURCE and $TARGET for backwards compatibility.
"""
# If $SOURCE and $TARGET are defined at the toplevel in "recdict", $TARGET
# is one item and there is no dependency with $TARGET as target that
# includes build commands, create one from $SOURCE and $TARGET. Can be
# used for the main and a child recipe.
if recdict.get("TARGET") and recdict.get("SOURCE"):
targets = varname2dictlist(recdict, None, "TARGET")
if len(targets) == 1:
msg_warning(recdict, _("Warning: support for $SOURCE and $TARGET will be removed soon"))
target = work.find_node(targets[0]["name"])
if not target or not target.get_first_build_dependency():
sources = varname2dictlist(recdict, None, "SOURCE")
add_buildrule([RecPos("Default target")], work, recdict,
"program", {}, targets, {}, sources)
def add_clean(work, recdict):
"""
Add "clean" build dependency for the current recipe if there isn't one.
Do the same for "cleanmore", but it also depends on "clean".
"""
for (trg, TRG, srclist) in [("clean", "CLEAN", []),
("cleanmore", "CLEANMORE", [{"name": "clean"}])]:
target = work.find_node(trg)
if not target or not target.get_recipe_build_dependency(recdict):
cmd = ( ("@if _recipe.get('%sFILES'):\n" % TRG)
+ (" :del {f} $_recipe.%sFILES\n" % TRG)
+ ("@if _recipe.get('%sDIRS'):\n" % TRG)
+ (" :del {f}{r} $_recipe.%sDIRS\n" % TRG))
msg_depend(recdict, _('Adding default dependency for "%s"') % trg)
rpstack = [RecPos("Default target")]
dep = Depend([{"name" : trg}], {}, srclist,
work, rpstack, cmd, recdict = recdict)
work.add_dependency(rpstack, dep)
# Add the generated recipes for automatic dependencies to $CLEANFILES.
# Go through all the dependencies and handle the ones defined in this
# recipe.
# TODO: this doesn't find automatic dependencies from rules and assumes
# dependencies are done recursively.
for dep in work.dependencies:
if (dep.buildrecdict
and dep.buildrecdict["_recipe"] is recdict["_recipe"]):
for src in dep.sourcelist:
node = work.find_node(src["name"])
if node and not node.did_add_clean:
node.did_add_clean = 1
ftype = node.get_ftype(recdict)
if ftype and ftype != "ignore":
action, recipe = find_autodep_items(work, recdict,
ftype, node, {})
if action:
add_cleanfiles(recdict, recipe.get_name())
# Remove duplicates from CLEANFILES.
if recdict["_recipe"].get("CLEANFILES"):
recdict["_recipe"]["CLEANFILES"] = rem_dup(
recdict["_recipe"]["CLEANFILES"])
def add_install_target(work, recdict, targetname, sourcenames, cmd):
"""
Call add_optional_target() twice, the second time with "install" changed to
"uninstall".
"""
add_optional_target(work, recdict, targetname, sourcenames, cmd)
untargetname = re.sub("install", "uninstall", targetname)
unsourcenames = map(lambda x: re.sub("install", "uninstall", x),
sourcenames)
# In the uninstall command change all "install" to "uninstall".
# When using INSTALL.* remove the ":update" line and then double the
# command to also use the UNINSTALL variable.
uncmd = re.sub("install", "uninstall", cmd)
if string.find(uncmd, "INSTALL") >= 0:
uncmd = re.sub(":update.*\n", "", uncmd)
uncmd = uncmd + re.sub("INSTALL", "UNINSTALL", uncmd)
add_optional_target(work, recdict, untargetname, unsourcenames, uncmd)
def add_optional_target(work, recdict, targetname, sourcenames, cmd):
"""
Add build dependency for "targetname" if there isn't one.
To be used at the toplevel.
"""
target = work.find_node(targetname)
if target and target.get_dependencies():
msg_depend(recdict, _('Not adding default dependency for "%s"')
% targetname)
else:
rpstack = [RecPos("Default target")]
if sourcenames:
sourcelist = map(lambda x: {"name": x}, sourcenames)
else:
sourcelist = []
dep = Depend([{"name" : targetname}], {}, sourcelist, work, rpstack,
cmd, recdict = recdict)
work.add_dependency(rpstack, dep)
def add_buildrule(rpstack, work, recdict,
type, cmd_attr, targetlist, build_attr, sourcelist):
"""
Add a buildrule. Also used for ":totype".
"type" is "program", "lib", "ltlib", "dll", "totype" or the first argument
of ":produce".
"cmd_attr" are extra attributes for the command itself.
"targetlist" is a dictlist with the target(s).
"build_attr" are extra attributes for the build commands.
"sourcelist" is a dictlist with the sources.
"""
# TODO: implement "cmd_attr".
if type != "totype":
target = work.find_node(targetlist[0]["name"])
if target and target.get_first_build_dependency():
msg_error(recdict, _("Both a build rule and a dependency with build commands defined for '%s'") % targetlist[0]["name"])
return
# Create a node for all items in sourcelist and targetlist, makes
# sure the attributes are carried over.
work.add_dictlist_nodes(sourcelist)
if type != "totype":
work.add_dictlist_nodes(targetlist)
# When there is no direct build action for the source or there
# are multiple sources, compile each source into an object and
# build all the objects into the target.
installvar = cmd_attr.get("installvar")
onestep = cmd_attr.get("onestep")
cleanfiles = []
if type == "dll":
if installvar is None:
installvar = "INSTALL_DLL"
elif type == "lib":
if installvar is None:
installvar = "INSTALL_LIB"
elif type == "ltlib":
if installvar is None:
installvar = "INSTALL_LTLIB"
elif type == "program":
if installvar is None:
installvar = "INSTALL_EXEC"
if onestep and type == "ltlib":
msg_warning(recdict, _("The 'onestep' attribute is not supported for the :ltlib target."), rpstack = rpstack)
onestep = None
if onestep:
add_onestep_build(rpstack, work, recdict, type, cmd_attr, targetlist,
build_attr, sourcelist)
else:
a = cmd_attr.get("objecttype")
if a:
objtypes = string.split(a, ',')
else:
objtypes = None
buildaction = cmd_attr.get("buildaction")
if type == "dll":
if not buildaction:
buildaction = "builddll"
elif type == "lib":
if not buildaction:
buildaction = "buildlib"
elif type == "ltlib":
if not buildaction:
buildaction = "buildltlib"
elif type == "program":
if not buildaction:
buildaction = "build"
elif type == "totype":
objtypes = [ targetlist[0]["name"] ]
else:
if not buildaction:
recipe_error(rpstack, _("Missing buildaction attribute"))
if installvar is None:
installvar = "INSTALL_EXEC"
if not objtypes:
# Obtain the supported object types from the buildaction.
act = find_primary_action(buildaction)
if not act:
if type not in ["dll", "lib", "ltlib", "program"]:
recipe_error(rpstack, _("Missing objecttype attribute"))
else:
recipe_error(rpstack, _("No primary %s action defined")
% buildaction)
objtypes = act.get_in_types()
objectsuffix = cmd_attr.get("objectsuffix")
if type == "totype" and targetlist[0].get("suffix"):
objectsuffix = targetlist[0].get("suffix")
objectprefix = cmd_attr.get("objectprefix")
if type == "totype" and targetlist[0].get("prefix"):
objectprefix = targetlist[0].get("prefix")
# Go over all source files and figure out a way to turn each one
# into an object file.
build_obj = []
for si in sourcelist:
node = work.get_node(si["name"], 1, si)
in_ftype = node.get_ftype(recdict)
if in_ftype in objtypes:
# The source is supported by the build action: add it directly.
build_obj.append(si)
else:
# Loop over the types that the build action supports, turn
# the source in the first one that is possible.
names = ''
objname = None
for objtype in objtypes:
if objtype == "default":
continue
objname = add_build_one(rpstack, work, recdict, node, si, in_ftype, objtype, objectprefix, objectsuffix, build_attr, cleanfiles)
if objname:
break
if names:
names = names + '/'
names = names + objtype
if objname is None:
recipe_error(rpstack, _('Do not know how to make an %s out of "%s"') % (names, si["name"]))
build_obj.append({"name" : objname})
if type != "totype":
targets_str = dictlist2str(targetlist)
if not onestep:
# Add a dependency to build the program or library in the form:
# targetlist : {buildcheck = xxx} sourcelist
# do buildaction {target = $target} $source
build_obj_str = dictlist2str(build_obj)
cmd = (" :do %s {target = $+target} $source" % buildaction)
msg_depend(recdict, _('Adding dependency:\n\t%s : %s\n\t%s')
% (targets_str, build_obj_str, cmd))
work.add_dependency(rpstack,
Depend(targetlist, build_attr, build_obj, work,
rpstack, cmd + '\n', recdict = recdict))
cleanfiles.extend(targetlist)
# Add target to INSTALL_EXEC, INSTALL_LIB or INSTALL_DLL.
# do that relative to the top directory.
if installvar:
append2var(recdict, "_top", installvar, topdir(targets_str))
# Add files to be cleaned to _recipe.CLEANFILES.
add_cleanfiles(recdict, dictlist2str(cleanfiles, Expand(0)))
# Add files to be distributed to _recipe.DISTFILES. Skip the ones with a
# {nodist} attribute.
l = filter(lambda x: not x.get("nodist"), sourcelist)
add_distfiles(recdict, dictlist2str(l, Expand(0)))
def add_build_one(rpstack, work, recdict, node, si, in_ftype,
objtype, objectprefix, objectsuffix, build_attr, cleanfiles):
"""
Add build rule to turn one source node into a file of type "objtype".
If successful return name of target file, None otherwise.
"""
sufname = None
if not objectsuffix:
if objtype == "dllobject":
sufname = "DLLOBJSUF"
elif objtype == "libobject":
sufname = "LIBOBJSUF"
elif objtype == "ltobject":
sufname = "LTOBJSUF"
elif objtype == "object":
sufname = "OBJSUF"
# Make the object name by prepending $BDIR and changing the
# extension to $OBJSUF/$LIBOBJSUF/$DLLOBJSUF/nothing.
n = srcitem2obj(recdict, si["name"], attrdict = si, sufname = sufname)
if objectprefix:
n = os.path.join(os.path.dirname(n), objectprefix + os.path.basename(n))
if objectsuffix:
n = n + objectsuffix
# If there is no build rule for the object file, add one.
target = work.find_node(n)
if target and target.get_first_build_dependency():
# There already is a dependency for this target.
if len(si) > 1:
msg_warning(recdict, _('%s: Ignoring attributes for "%s", a build rule already exists') % (str(rpstack[-1]), si["name"]))
else:
# Find the route to the object file.
route = work.find_route(in_ftype, objtype, use_actions = 1)
if not route:
return None
# Add attributes from ":route" to the target node.
work.get_node(n, add = 1, dict = route.targetattr)
# Inits to avoid a warning from pychecker.
trg = None
out_ftype = None
# Loop over all steps in the route.
for i in range(len(route.steplist)):
# Get the action and filename by processing the line.
# TODO: catch errors
route.rpstack[-1].line_nr = route.lnumlist[i] - 1
fp = ParsePos(route.rpstack, string = "_x = "
+ route.steplist[i] + '\n')
rd = get_build_recdict(recdict, route.recdict,
route.rpstack, keep_current_scope = 1,
xscope = build_attr.get("scope"))
rd["source"] = si["name"]
Process(fp, rd, 0)
l = string.split(rd["_x"], None, 1)
action = l[0]
if i == 0:
src_dict = si.copy()
src = si["name"]
dnode = node
else:
src_dict = {"name" : trg}
src = trg
in_ftype = out_ftype
dnode = work.find_node(src)
# Set "depdir" for the auto-depend recipe. Required if the source
# file is built twice with different $BDIR. Also add the recipe to
# the files to be cleaned now, because $BDIR may change afterwards.
if not src_dict.get("depdir"):
src_dict["depdir"] = get_var_val_int(recdict, "BDIR")
depaction, recipe = find_autodep_items(work, recdict,
in_ftype, dnode, src_dict)
if depaction:
add_cleanfiles(recdict, recipe.get_name())
if i == len(route.steplist) - 1:
trg = n
out_ftype = objtype
else:
trg = l[1]
out_ftype = route.typelist[i + 1][0]
if (not os.path.isabs(trg) and string.find(trg, "build-") != 0):
trg = os.path.join(get_var_val_int(recdict, "BDIR"), trg)
# Add a dependency to execute the action.
cmd = " :do %s {target = %s} %s" % (action, trg, src)
msg_depend(recdict, _('Adding dependency:\n\t%s : %s\n\t%s')
% (trg, dictlist2str([src_dict]), cmd))
src_dict["filetype"] = in_ftype
trg_dict = {"name": trg, "filetype" : out_ftype}
work.add_dependency(rpstack,
Depend([ trg_dict ], build_attr, [ src_dict ], work,
rpcopy(route.rpstack, route.lnumlist[i]),
cmd + '\n', recdict = recdict))
cleanfiles.append({"name": trg})
return n
def add_onestep_build(rpstack, work, recdict,
type, cmd_attr, targetlist, build_attr, sourcelist):
"""Adds an onestep build"""
buildaction = cmd_attr.get("buildaction")
if type == "dll":
if not buildaction:
buildaction = "builddllonestep"
elif type == "lib":
if not buildaction:
buildaction = "buildlibonestep"
elif type == "program":
if not buildaction:
buildaction = "buildonestep"
cmd = (" :do %s {target =$+target} $source" % buildaction)
work.add_dependency(rpstack,
Depend(targetlist, build_attr, sourcelist, work,
rpstack, cmd + '\n', recdict = recdict))
# vim: set sw=4 et sts=4 tw=79 fo+=l:
|