Dynamic task generators - build

I am evaluating waf build for an existing project that has tasks similar to that:
1. preprocessing phase: datafile => TransformationTask => library name list
2. for each library name:
2.1 import files from repository
2.2 build library
The library list depends on the preprocessing task and is naturally not known in advance.
How can this be achieved with waf?

You have to generate a file with the library list with a first task. Another task will have the output of the first as an input and will process the corresponding file if needed to generate what you need.
It is somehow the example given in §11.4.2 of the waf book. You have to replace the compiler output parsing with your library description file parsing. You need to copy the example and change the run method in mytool.py like:
class src2c(Task.Task):
color = 'PINK'
quiet = True
before = ['cstlib']
def run(self):
libnode = self.inputs[0]
libinfo = libnode.read_json()
name = libinfo['name']
files = [f"repo/{file}" for file in libinfo['files']]
taskgen = self.generator
# library name
taskgen.link_task.outputs = []
taskgen.link_task.add_target(name)
# library sources files
nodes = [taskgen.path.make_node(f) for f in files]
# update discovered dependancies
taskgen.bld.raw_deps[self.uid()] = [self.signature()] + nodes
with g_lock:
self.add_c_tasks(nodes)
# cf waf book § 11.4.2
def add_c_tasks(self, lst):
...
# cf waf book § 11.4.2
def runnable_status(self):
...
In the wscript, I simulate the datafile transformation with a copy.:
def options(opt):
opt.load("compiler_c")
def configure(cnf):
cnf.load("compiler_c")
cnf.load("mytool", tooldir=".")
def build(bld):
bld(source = "libs.json", target = "libs.src", features = "subst")
bld(source = "libs.src", features = ["c", "cstlib"])
With a simple my_lib.json:
{
"name": "mylib2",
"files": ["f1.c", "f2.c"]
}
And files repo/f1.c and repo/f2.c like void f1(){} and void f2(){}

Related

How to download a file in Bazel from a BUILD file?

Is there a way to download a file in Bazel directly from a BUILD file? I know I can probably use wget and enable networking, but I'm looking for a solution that would work with bazel fetch.
I have a bunch of files to download that are going to be consumed by just a single package. It feels wrong to use the standard approach of adding a http_file() rule in WORKSPACE at the monorepo root. It would be decoupled from the package and it would pollute a totally unrelated file.
Create a download.bzl and load it in your WORKSPACE file
WORKSPACE:
load("//my_project/my_sub_project:download.bzl", "downlad_files")
load("#bazel_skylib//rules:copy_file.bzl", "copy_file")
download_files()
download.bzl:
BUILD_FILE_CONTENT_some_3d_model = """
filegroup(
name = "some_3d_model",
srcs = [
"BMW_315_DA2.obj",
],
visibility = ["//visibility:public"],
)
"""
def download_files():
http_archive(
name = "some_3d_model",
build_file_content = BUILD_FILE_CONTENT_some_3d_model,
#sha256 = "...",
urls = ["https://vertexwahn.de/lfs/v1/some_3d_model.zip"],
)
copy_file(
name = "copy_resources_some_3d_model",
src = "#some_3d_model",
out = "my/destination/path/some_file.obj",
)

accessing recources when compiling QT application with Bazel

I'm using Bazel to compile a Qt application (https://github.com/bbreslauer/qt-bazel-example) that is using shaders defined in a qrc file.
When I'm trying to access the resource file, it is not available (as I did not connect the qrc file to the compilation).
How can I define the qrc file content in the build?
UPDATE
following the response by #ypnos, I'm trying to add a macro to my qt.bzl file. I would like the macro to recieve a list of files as an argument, create the (temporary) qrc file, and run the rcc command.
I am currently struggling with:
running a python script in the bzl file is not as straightforward as I though. It cannot generate a file ("open" is undefined). Is it possible? if yes how (see example below)
even with a given qrc file, I cant get the command to work, I guess i'm doing somthing wrong with the command line arguments but I cant find refrence/manual for that
this is what I got so far(my qt.bzl file)
...
def qt_resource(name,file_list, **kwargs):
## following doesnt work inside the bzl file:
# fid = open('%s.qrc' % name, 'w')
# fid.write("<RCC>\n")
# fid.write("\t<qresource prefix=\"/%s\">\n" % name)
# for x in file_list:
# fid.write("\t\t<file>%s</file>\n" % x)
# fid.write("\t</qresource>\n")
# fid.write("</RCC>\n")
# fid.close()
native.genrule(
name = "%s_res" % name,
outs = ["rcc_%s.cpp" % name],
cmd = "rcc %s.qrc -o $#/rcc_%s.cpp"%(name,name) ,
)
srcs = [":rcc_%s.cpp" % name]
native.cc_library(
name = name,
srcs = srcs,
hdrs = [],
deps = [],
**kwargs
)
It seems the bazel example that you are using does not come with support for qrc (it only does moc and ui files).1
QRC files need to be transformed into C++ sources using rcc and then compiled.2 The concept is similar to the one of .ui files which are converted to headers.
Maybe you can patch qt.bzl to add that functionality.

How can I make a library dependency graph with waf?

I'd like to generate a simple DOT file when building a C++ project with waf. Ideally I'd like to just use the use and target attributes of the bld command to generate the file. Is this easily injectable into the system?
e.g. This wscript file (just mentioning the parts I'd like to use)
def build(bld):
bld( use = [ 'lib1',
'lib2', ] ,
target = 'lib3' )
Would produce output of
lib3 -> lib1
lib3 -> lib2
Where would be the best place to inject this behavior?
Thanks!
You can add add a tool like this easily via the add_post_fun in the build step, something along like this:
from waflib.Errors import WafError
from waflib import Utils
def filter_uses(ctx, uses):
filtered = []
for use in uses:
try:
ctx.get_tgen_by_name(use)
filtered.append(use)
except WafError:
pass
return filtered
#Utils.run_once # print only once, even if used in multiple script
def make_dot_file(ctx):
for group in ctx.groups:
for taskgen in group:
uses = Utils.to_list(getattr(taskgen, 'use', []))
uses = filter_uses(ctx, uses) # Optional, only print TaskGens
try:
name = taskgen.name # Sometimes this fails, don't know why
print "{} -> {}".format(name, ", ".join(uses))
except AttributeError:
pass
def build(bld):
# Build stuff ...
bld.add_post_fun(make_dot_file)
Note: To get real nice output some more filtering might be useful
I improved and adjusted #CK1 idea to my needs. My solution generates a DAG with graphviz and uses helper functions from this article by Matthias Eisen to display dependencies and targets.
The main part of the code looks like this:
import functools
import graphviz as gv
from pathlib import Path
from waflib import Utils
# Make sure that dot.exe is in your system path. I had to do this as
# Graphviz (the program, not the package) is installed with conda. I am
# sure there is a proper way to do this with Waf.
library_bin = Path(sys.executable).parent / 'Library' / 'bin' / 'graphviz'
os.environ['PATH'] += str(library_bin) + ';'
def make_dot_file(ctx):
# Create DAG
dag = digraph()
# Loop over task groups
for group in ctx.groups:
# Loop over tasks
for taskgen in group:
# Get name and add node for task
name = taskgen.get_name()
add_nodes(dag, [name])
# Add nodes for dependencies and edges to task
deps = Utils.to_list(getattr(taskgen, 'deps', []))
for dep in deps:
dep = Path(dep).name
add_nodes(dag, [dep])
add_edges(dag, [(dep, name)])
# Add nodes for targets and edges to task
targets = Utils.to_list(getattr(taskgen, 'target', []))
for target in targets:
target = Path(target).name
add_nodes(dag, [target])
add_edges(dag, [(name, target)])
# Make the DAG pretty
dag = apply_styles(dag, styles)
# Save DAG
dag.render(<output path of graphic>)
def build(bld):
# Build stuff ...
bld.add_post_fun(make_dot_file)
The helper functions used for this example are here:
# -------------------- Start helper functions ----------------------------
graph = functools.partial(gv.Graph, format='png')
digraph = functools.partial(gv.Digraph, format='png')
styles = {
'graph': {
'label': 'Pretty Graph',
'fontsize': '16',
'fontcolor': 'white',
'bgcolor': '#333333',
'rankdir': 'BT',
},
'nodes': {
'fontname': 'Helvetica',
'shape': 'hexagon',
'fontcolor': 'white',
'color': 'white',
'style': 'filled',
'fillcolor': '#006699',
},
'edges': {
'style': 'dashed',
'color': 'white',
'arrowhead': 'open',
'fontname': 'Courier',
'fontsize': '12',
'fontcolor': 'white',
}
}
def apply_styles(graph, styles):
graph.graph_attr.update(
('graph' in styles and styles['graph']) or {}
)
graph.node_attr.update(
('nodes' in styles and styles['nodes']) or {}
)
graph.edge_attr.update(
('edges' in styles and styles['edges']) or {}
)
return graph
def add_nodes(graph, nodes):
for n in nodes:
if isinstance(n, tuple):
graph.node(n[0], **n[1])
else:
graph.node(n)
return graph
def add_edges(graph, edges):
for e in edges:
if isinstance(e[0], tuple):
graph.edge(*e[0], **e[1])
else:
graph.edge(*e)
return graph
# ----------------------- End helper functions -----------------------------

py2app - preserve directory structure

I would like to make a mac os x app for distributing my program.
The problem is that current script puts "images/Diagram.png" and other files under "images" folder to "Resources" but not to "Resources/images" as expected
What can I change in this setup.py part in order to put png files under images?
mainscript, ico, icns = "aproxim.py", "sum.ico", "sum.icns"
files = ["images/Diagram.png", "images/document.png", "images/Exit.png", "images/floppydisc.png",
"images/folder.png", "images/Info.png", "images/settings.png"]
scripts = ["app_settings_dialog", "app_window", "approximator", "approximator2", "data_format",
"easy_excel", "functions", "main_window", "settings_dialog",
"util_data", "util_parameters"]
description = "Approximator is program for experimental data approximation and interpolation (20 dependencies for select)"
common_options = dict(name = "Approximator", version = "1.7", description = description)
if sys.platform == 'darwin':
setup(
setup_requires = ['py2app'],
app = [mainscript],
options = dict(py2app = dict(includes = scripts, resources = files, iconfile = icns)),
**common_options)
It is easy, hope it is useful for other Python developers:
resources = [('images', files), ('', [ico])]

SCons: How can I attach a listener?

I need to attach to the SCons build in order to be notified when things happened during it: file compilation, files linkage, etc.
I know similar is possible for ANT builds via -listener option. Could you please tell how to do it for SCons builds?
When targets are built in SCons, you can associate a post action via the AddPostAction(target, action) function as documented here.
Here is a simple example with a python function action:
# Create yourAction here:
# can be a python function or external (shell) command line
def helloWorldAction(target = None, source = None, env = None):
'''
target: a Node object representing the target file
source: a Node object representing the source file
env: the construction environment used for building the target file
The target and source arguments may be lists of Node objects if there
is more than one target file or source file.
'''
print "PostAction for target: %s" % str(target)
# you can get a map of the source files like this:
# source_file_names = map(lambda x: str(x), source)
# compilation options, etc can be retrieved from the env
return 0
env = Environment()
progTarget = env.Program(target = "helloWorld", source = "helloWorld.cc")
env.AddPostAction(progTarget, helloWorldAction)
# Or create the action object like this:
# a = Action(helloWorldAction)
Then, each time helloWorld is built, the helloWorldAction python function will be executed afterwords.
Regarding doing this without modifying the given SConstruct, I dont see how that would be possible.