in pkg/rbeconfigsgen/rbeconfigsgen.go [435:544]
func genCppConfigs(d *dockerRunner, o *Options, bazelPath string) (string, error) {
if !o.GenCPPConfigs {
return "", nil
}
// Change the working directory to a dedicated empty directory for C++ configs for each
// command we run in this function.
cppProjDir := path.Join(d.workdir, "cpp_configs_project")
if _, err := d.execCmd("mkdir", cppProjDir); err != nil {
return "", fmt.Errorf("failed to create empty directory %q inside the toolchain container: %w", cppProjDir, err)
}
oldWorkDir := d.workdir
d.workdir = cppProjDir
defer func() {
d.workdir = oldWorkDir
}()
if _, err := d.execCmd("touch", "WORKSPACE", "BUILD.bazel"); err != nil {
return "", fmt.Errorf("failed to create empty build & workspace files in the container to initialize a blank Bazel repository: %w", err)
}
// Backup the current environment & restore it before returning.
oldEnv := d.env
defer func() {
d.env = oldEnv
}()
// Create a new environment for bazelisk commands used to specify the Bazel version to use to
// Bazelisk.
bazeliskEnv := []string{fmt.Sprintf("USE_BAZEL_VERSION=%s", o.BazelVersion)}
// Add the environment variables needed for the generation only and remove them immediately
// because they aren't necessary for the config extraction and add unnecessary noise to the
// logs.
generationEnv, err := appendCppEnv(bazeliskEnv, o)
if err != nil {
return "", fmt.Errorf("failed to add additional environment variables to the C++ config generation docker command: %w", err)
}
d.env = generationEnv
cmd := []string{
bazelPath,
o.CppBazelCmd,
}
cmd = append(cmd, o.CPPConfigTargets...)
if _, err := d.execCmd(cmd...); err != nil {
return "", fmt.Errorf("Bazel was unable to build the C++ config generation targets in the toolchain container: %w", err)
}
// Restore the env needed for Bazelisk.
d.env = bazeliskEnv
bazelOutputRoot, err := d.execCmd(bazelPath, "info", "output_base")
if err != nil {
return "", fmt.Errorf("unable to determine the build output directory where Bazel produced C++ configs in the toolchain container: %w", err)
}
cppConfigDir := path.Join(bazelOutputRoot, "external", o.CPPConfigRepo)
log.Printf("Extracting C++ config files generated by Bazel at %q from the toolchain container.", cppConfigDir)
// Restore the old env now that we're done with Bazelisk commands. This is purely to reduce
// noise in the logs.
d.env = oldEnv
// 1. Get a list of symlinks in the config output directory.
// 2. Harden each link.
// 3. Archive the contents of the config output directory into a tarball.
// 4. Copy the tarball from the container to the local temp directory.
var out string
if o.ExecOS == "windows" {
out, err = d.execCmd("cmd", "/r", "dir", filepath.Clean(cppConfigDir), "/a:l", "/b")
} else {
out, err = d.execCmd("find", cppConfigDir, "-type", "l")
}
if err != nil {
errMsg := fmt.Sprintf("unable to list symlinks in the C++ config generation build output directory: ")
// Windows `dir` has a non-zero exit status if no files are found.
// Linux just doesn't return any files but has a zero exit.
switch o.ExecOS {
case "windows":
out = ""
log.Printf("Ignoring error indicating no symlinks were found in the Bazel output directory: %v", err)
default:
return "", fmt.Errorf("%s%w", errMsg, err)
}
}
symlinks := strings.Split(out, "\n")
for _, s := range symlinks {
if s == "" {
continue
}
resolvedPath, err := d.execCmd("readlink", s)
if err != nil {
return "", fmt.Errorf("unable to determine what the symlink %q in %q in the toolchain container points to: %w", s, cppConfigDir, err)
}
if _, err := d.execCmd("ln", "-f", resolvedPath, s); err != nil {
return "", fmt.Errorf("failed to harden symlink %q in %q pointing to %q: %w", s, cppConfigDir, resolvedPath, err)
}
}
outputTarball := "cpp_configs.tar"
// Explicitly use absolute paths to avoid confusion on what's the working directory.
outputTarballPath := path.Join(o.TempWorkDir, outputTarball)
outputTarballContainerPath := path.Join(cppProjDir, outputTarball)
if _, err := d.execCmd("tar", "-cf", outputTarballContainerPath, "-C", cppConfigDir, "."); err != nil {
return "", fmt.Errorf("failed to archive the C++ configs into a tarball inside the toolchain container: %w", err)
}
if err := d.copyFromContainer(outputTarballContainerPath, outputTarballPath); err != nil {
return "", fmt.Errorf("failed to copy the C++ config tarball out of the toolchain container: %w", err)
}
log.Printf("Generated C++ configs at %s.", outputTarballPath)
return outputTarballPath, nil
}