blob: ad23ae85df54583cc3f9159fb7c55c6ef3d6e7cd [file] [log] [blame]
// 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
}