8

I am trying to make a following setup run with Bazel. With calling “bazel build” a Python script should generate unknown number of *.cc files with random names, and then compile these into single static library (.a file), all within one Bazel call. I have tried following: one generated file has fixed name, this one is referenced in outs of genrule() and srcs of cc_library rule. The problem is I need all generated files to be built as a library, not only the file with fixed name. Any ideas how to do this? My BUILD file:

py_binary(
    name = "sample_script",
    srcs = ["sample_script.py"],
)
genrule(
    name = "sample_genrule",
    tools = [":sample_script"],
    cmd = "$(location :sample_script)",
    outs = ["cpp_output_fixed.cc"], #or shall also the files with random names be defined here?
)
cc_library(
    name = "autolib",
    srcs = ["cpp_output_fixed.cc"],
    #srcs = glob([ #here should all generated .cc files be named
    #    "./*.cc",
    #    "./**/*.cc",
    #    ])+["cpp_output_fixed.cc"], 
)

Python file sample_script.py:

#!/usr/bin/env python
import hashlib
import time

time_stamp = time.time()

time_1 = str(time_stamp)
time_2 = str(time_stamp + 1)

random_part_1 = hashlib.sha1(time_1).hexdigest()[-4:]
random_part_2 = hashlib.sha1(time_1).hexdigest()[-4:]

fixed_file = "cpp_output_fixed" + ".cc"
file_1 = "cpp_output_" + random_part_1 + ".cc"
file_2 = "cpp_output3_" + random_part_2 + ".cc"

with open(fixed_file, "w") as outfile:
    outfile.write("#include <iostream>"
                   "int main() {"
                   "  std::cout <<'''Hello_world'''    <<std::endl;"
                   "  return 0"
                   "}")

with open(file_1, "w") as outfile:
    outfile.write("#include <iostream>"
                   "int main() {"
                   "  std::cout <<'''Hello_world'''    <<std::endl;"
                   "  return 0"
                   "}")

with open(file_2, "w") as outfile_new:
    outfile_new.write("#include <iostream>"
                   "int main() {"
                   "  std::cout <<'''Hello_world''' <<std::endl;"
                   "  return 0"
                   "}")

print ".cc generation DONE"
Manan Maqbool
  • 123
  • 2
  • 11
  • seems duplicate of https://stackoverflow.com/questions/44312549/bazel-automatically-generated-cpp-hpp-files – Bhavik Jan 25 '18 at 04:19
  • @Bhavik In that part only one or two files were generated but in my case the names of the files(generated) would be unknown/random and it also not known how many files would be generated. The files are generated with **bazel-bin ///** and cc_library rule is executed during **bazel build :** – Manan Maqbool Jan 25 '18 at 09:47

2 Answers2

16

[big edit, since I found a way to make it work :)]

If you really need to emit files that are unknown at the analysis phase, your only way is what we internally call tree artifacts. You can think of it as a directory that contains files that will only be inspected at the execution phase. You can declare a tree artifact from Skylark using ctx.actions.declare_directory.

Here is a working example. Note 3 things:

  • we need to add ".cc" to the directory name to fool C++ rules that this is valid input
  • the generator needs to create the directory that bazel tells it to
  • you need to use bazel@HEAD (or bazel 0.11.0 and later)

genccs.bzl:

def _impl(ctx):
  tree = ctx.actions.declare_directory(ctx.attr.name + ".cc")
  ctx.actions.run(
    inputs = [],
    outputs = [ tree ],
    arguments = [ tree.path ],
    progress_message = "Generating cc files into '%s'" % tree.path,
    executable = ctx.executable._tool,
  )

  return [ DefaultInfo(files = depset([ tree ])) ]

genccs = rule(
  implementation = _impl,
  attrs = {
    "_tool": attr.label(
      executable = True,
      cfg = "host",
      allow_files = True,
      default = Label("//:genccs"),
    )
  }
)

BUILD:

load(":genccs.bzl", "genccs")

genccs(
    name = "gen_tree",
)

cc_library(
    name = "main",
    srcs = [ "gen_tree" ]
)

cc_binary(
    name = "genccs",
    srcs = [ "genccs.cpp" ],
)

genccs.cpp

#include <fstream>
#include <sys/stat.h>
using namespace std;

int main (int argc, char *argv[]) {
  mkdir(argv[1], S_IRWXU);
  ofstream myfile;
  myfile.open(string(argv[1]) + string("/foo.cpp"));
  myfile << "int main() { return 42; }";
  return 0;
}
hlopko
  • 3,100
  • 22
  • 27
  • do you suggest to use ctx.actions.declare_directory as output folder for custom rule that is using ctx.action.run_shell to generate the files ? – Manan Maqbool Jan 30 '18 at 15:26
  • Yup, something like that :) – hlopko Jan 30 '18 at 15:33
  • Thanks alot for your support, it worked perfectly :) – Manan Maqbool Feb 01 '18 at 12:19
  • @bazel-discuss FYI – Manan Maqbool Feb 01 '18 at 12:20
  • And a related cl has been rolled back, subscribe to https://bazel-review.googlesource.com/c/bazel/+/35694 to see when it's coming back. – hlopko Feb 01 '18 at 16:42
  • I´m trying to add now a cc_binary rule that is using the library as a dependency in "deps", but it gives in error "cc1plus: fatal error: bazel-out/k8-fastbuild/bin/main/_pic_objs/main-library/main/collected_collectsrcs.cc: No such file or directory compilation terminated. " is it because I use the folder? is Bazel able to use the library with cc_binary? – Manan Maqbool Feb 20 '18 at 09:19
  • Hmm might be the same bug we observed internally. The fix is being submitted today/tomorrow. – hlopko Mar 15 '18 at 13:58
  • @hlopko This "trick" doesn't seem to work on Windows, correct? – ale64bit Sep 02 '19 at 21:24
2

1) List all output files. 2) Use the genrule as a dependency to the library.

genrule(
    name = "sample_genrule",
    tools = [":sample_script"],
    cmd = "$(location :sample_script)",
    outs = ["cpp_output_fixed.cc", "cpp_output_0.cc", ...]
)

cc_library(
    name = "autolib",
    srcs = [":sample_genrule"],
)
Laurent
  • 2,951
  • 16
  • 19
  • It gives me following error: **declared output 'main1/cpp_output_fixed.cc' was not created by genrule. This is probably because the genrule actually didn't create this output, or because the output was a directory and the genrule was run remotely (note that only the contents of declared file outputs are copied from genrules run remotely** and I may not know the names of the generated files, that is also another aspect to be considered. – Manan Maqbool Jan 30 '18 at 12:32
  • Recommendation is to organize your build so that all dependencies are known. Having a script that generates random files based on the time is not recommended, and it's definitely not reproducible. You could pass some information to your script on the command-line (e.g. list of files that are expected). – Laurent Jan 31 '18 at 12:39