func genCppConfigs()

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
}