// Copyright 2023 Google LLC // // Use of this source code is governed by a BSD-style license that can be // found in the LICENSE file. package common import ( "archive/zip" "context" "fmt" "io" "os" "path/filepath" "regexp" "strings" "go.skia.org/infra/go/skerr" "go.skia.org/infra/go/util" "go.skia.org/infra/task_driver/go/lib/os_steps" "go.skia.org/infra/task_driver/go/td" ) // validBazelLabelRegexps represent valid, fully-qualified Bazel labels. var validBazelLabelRegexps = []*regexp.Regexp{ regexp.MustCompile(`^//:[a-zA-Z0-9_-]+$`), // Matches "//:foo". regexp.MustCompile(`^/(/[a-zA-Z0-9_-]+)+:[a-zA-Z0-9_-]+$`), // Matches "//foo:bar", "//foo/bar:baz", etc. } // ValidateLabelAndReturnOutputsZipPath validates the given Bazel label and returns the path within // the checkout directory where the ZIP archive with undeclared test outputs will be found, if // applicable. func ValidateLabelAndReturnOutputsZipPath(checkoutDir, label string) (string, error) { valid := false for _, re := range validBazelLabelRegexps { if re.MatchString(label) { valid = true break } } if !valid { return "", skerr.Fmt("invalid label: %q", label) } return filepath.Join( checkoutDir, "bazel-testlogs", strings.ReplaceAll(strings.TrimPrefix(label, "//"), ":", "/"), "test.outputs", "outputs.zip"), nil } // ExtractOutputsZip extracts the undeclared outputs ZIP archive into a temporary directory, and // returns the path to said directory. func ExtractOutputsZip(ctx context.Context, outputsZipPath string) (string, error) { // Create extraction directory. extractionDir, err := os_steps.TempDir(ctx, "", "bazel-test-output-dir-*") if err != nil { return "", skerr.Wrap(err) } // Extract ZIP archive. if err := td.Do(ctx, td.Props(fmt.Sprintf("Extract undeclared outputs archive %s into %s", outputsZipPath, extractionDir)), func(ctx context.Context) error { outputsZip, err := zip.OpenReader(outputsZipPath) if err != nil { return skerr.Wrap(err) } defer util.Close(outputsZip) for _, file := range outputsZip.File { // Skip directories. We assume all output files are at the root directory of the archive. if file.FileInfo().IsDir() { if err := td.Do(ctx, td.Props(fmt.Sprintf("Not extracting subdirectory: %s", file.Name)), func(ctx context.Context) error { return nil }); err != nil { return skerr.Wrap(err) } continue } // Skip files within subdirectories. if strings.Contains(file.Name, "/") { if err := td.Do(ctx, td.Props(fmt.Sprintf("Not extracting file within subdirectory: %s", file.Name)), func(ctx context.Context) error { return nil }); err != nil { return skerr.Wrap(err) } continue } // Ignore anything that is not a PNG or JSON file. if !strings.HasSuffix(strings.ToLower(file.Name), ".png") && !strings.HasSuffix(strings.ToLower(file.Name), ".json") { if err := td.Do(ctx, td.Props(fmt.Sprintf("Not extracting non-PNG / non-JSON file: %s", file.Name)), func(ctx context.Context) error { return nil }); err != nil { return skerr.Wrap(err) } continue } // Extract file. if err := td.Do(ctx, td.Props(fmt.Sprintf("Extracting file: %s", file.Name)), func(ctx context.Context) error { reader, err := file.Open() if err != nil { return skerr.Wrap(err) } defer util.Close(reader) bytes, err := io.ReadAll(reader) if err != nil { return skerr.Wrap(err) } return skerr.Wrap(os.WriteFile(filepath.Join(extractionDir, file.Name), bytes, 0644)) }); err != nil { return skerr.Wrap(err) } } return nil }); err != nil { return "", skerr.Wrap(err) } return extractionDir, nil }