blob: c2bc953a1a6d43b6eca601909392408a86e21b36 [file] [log] [blame] [edit]
// Copyright 2022 The Chromium Authors. All rights reserved.
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.
package main
import (
"encoding/json"
"flag"
"fmt"
"os"
"regexp"
"strings"
)
type depConfig struct {
bazelNameOverride string // Bazel style uses underscores not dashes, so we fix those if needed.
needsBazelFile bool
patches []string
patchCmds []string
patchCmdsWin []string
isIndirect bool // if True, it's used by another dependency, not by Skia directly
}
// These are all C++ deps or Rust deps (with a compatible C++ FFI) used by the Bazel build.
// They are a subset of those listed in DEPS.
// The key is the name of the repo as specified in DEPS.
var depsOverrides = map[string]depConfig{
"abseil-cpp": {bazelNameOverride: "abseil_cpp", isIndirect: true},
"brotli": {isIndirect: true},
"highway": {isIndirect: true},
"spirv-tools": {bazelNameOverride: "spirv_tools"},
// This name is important because spirv_tools expects @spirv_headers to exist by that name.
"spirv-headers": {bazelNameOverride: "spirv_headers"},
"dawn": {needsBazelFile: true},
"delaunator-cpp": {bazelNameOverride: "delaunator", needsBazelFile: true},
"dng_sdk": {needsBazelFile: true},
"expat": {
needsBazelFile: true,
patches: []string{"//bazel/external/expat:config_files.patch"},
},
"freetype": {
needsBazelFile: true,
patches: []string{"//bazel/external/freetype:config_files.patch"},
},
"harfbuzz": {
needsBazelFile: true,
patches: []string{"//bazel/external/harfbuzz:config_files.patch"},
},
"icu": {
needsBazelFile: true,
patches: []string{"//bazel/external/icu:icu_utils.patch"},
patchCmds: []string{
"rm source/i18n/BUILD.bazel",
"rm source/common/BUILD.bazel",
"rm source/stubdata/BUILD.bazel",
},
patchCmdsWin: []string{
"del source/i18n/BUILD.bazel",
"del source/common/BUILD.bazel",
"del source/stubdata/BUILD.bazel",
},
},
"icu4x": {needsBazelFile: true},
"imgui": {needsBazelFile: true},
"libavif": {needsBazelFile: true},
"libgav1": {needsBazelFile: true},
"libjpeg-turbo": {bazelNameOverride: "libjpeg_turbo", needsBazelFile: true},
"libjxl": {needsBazelFile: true},
"libpng": {needsBazelFile: true},
"libwebp": {needsBazelFile: true},
"libyuv": {needsBazelFile: true},
"spirv-cross": {bazelNameOverride: "spirv_cross", needsBazelFile: true},
"perfetto": {needsBazelFile: true},
"piex": {needsBazelFile: true},
"vello": {needsBazelFile: true},
"vulkan-headers": {bazelNameOverride: "vulkan_headers", needsBazelFile: true},
"vulkan-tools": {bazelNameOverride: "vulkan_tools", needsBazelFile: true},
"vulkan-utility-libraries": {bazelNameOverride: "vulkan_utility_libraries", needsBazelFile: true},
"vulkanmemoryallocator": {needsBazelFile: true},
"wuffs": {needsBazelFile: true},
"zlib": {needsBazelFile: true},
}
func main() {
var (
depsFile = flag.String("deps_file", "DEPS", "The location of the DEPS file. Usually at the root of the repository")
genJSONFile = flag.String("gen_json_file", "bazel/deps.json", "The location of the .json file that has the generated data used to make Bazel git_repository rules.")
// https://bazel.build/docs/user-manual#running-executables
repoDir = flag.String("repo_dir", os.Getenv("BUILD_WORKSPACE_DIRECTORY"), "The root directory of the repo. Default set by BUILD_WORKSPACE_DIRECTORY env variable.")
)
flag.Parse()
if *repoDir == "" {
fmt.Println(`Must set --repo_dir
This is done automatically via:
bazel run //bazel/deps_parser`)
os.Exit(1)
}
fmt.Println(os.Environ())
if *depsFile == "" || *genJSONFile == "" {
fmt.Println("Must set --deps_file and --gen_json_file")
flag.PrintDefaults()
}
if err := os.Chdir(*repoDir); err != nil {
fmt.Printf("Could not cd to %s\n", *repoDir)
os.Exit(1)
}
b, err := os.ReadFile(*depsFile)
if err != nil {
fmt.Printf("Could not open %s: %s\n", *depsFile, err)
os.Exit(1)
}
contents := strings.Split(string(b), "\n")
outputFile, count, err := parseDEPSFile(contents)
if err != nil {
fmt.Printf("Parsing error %s\n", err)
os.Exit(1)
}
if err := moveWithCopyBackup(outputFile, *genJSONFile); err != nil {
fmt.Printf("Could not write to generated .bzl file: %s\n", err)
os.Exit(1)
}
fmt.Printf("Wrote %d deps\n", count)
}
type repoConfig struct {
// https://bazel.build/rules/lib/repo/git
BuildFile string `json:"build_file,omitempty"`
Commit string `json:"commit"`
Name string `json:"name"`
PatchCmds []string `json:"patch_cmds,omitempty"`
PatchCmdsWin []string `json:"patch_cmds_win,omitempty"`
Patches []string `json:"patches,omitempty"`
Remote string `json:"remote"`
}
type deps struct {
Warning string `json:"!note"`
Direct []repoConfig `json:"direct"`
Indirect []repoConfig `json:"indirect"`
}
func parseDEPSFile(contents []string) (string, int, error) {
depsLine := regexp.MustCompile(`externals/(\S+)".+"(https.+)@([a-f0-9]+)"`)
outputFile, err := os.CreateTemp("", "genbzl")
if err != nil {
return "", 0, fmt.Errorf("Could not create output file: %s\n", err)
}
defer outputFile.Close()
var d deps
d.Warning = "DO NOT MODIFY BY HAND. Instead, bazelisk run //bazel/deps_parser"
for _, line := range contents {
if match := depsLine.FindStringSubmatch(line); len(match) > 0 {
rc := repoConfig{Name: match[1], Remote: match[2], Commit: match[3]}
cfg, ok := depsOverrides[rc.Name]
if !ok {
continue
}
if cfg.bazelNameOverride != "" {
rc.Name = cfg.bazelNameOverride
}
rc.Patches = cfg.patches
rc.PatchCmds = cfg.patchCmds
rc.PatchCmdsWin = cfg.patchCmdsWin
if cfg.needsBazelFile {
rc.BuildFile = fmt.Sprintf("//bazel/external/%s:BUILD.bazel", rc.Name)
}
if cfg.isIndirect {
d.Indirect = append(d.Indirect, rc)
} else {
d.Direct = append(d.Direct, rc)
}
}
}
je := json.NewEncoder(outputFile)
je.SetIndent("", " ")
if err := je.Encode(d); err != nil {
return "", 0, fmt.Errorf("Could not encode json file: %s\n", err)
}
return outputFile.Name(), len(d.Direct) + len(d.Indirect), nil
}
func moveWithCopyBackup(src, dst string) error {
// Atomically rename temp file to workspace. This should minimize the chance of corruption
// or writing a partial file if there is an error or the program is interrupted.
if err := os.Rename(src, dst); err != nil {
// Errors can happen if the temporary file is on a different partition than the Skia
// codebase. In that case, do a manual read/write to copy the data. See
// https://github.com/jenkins-x/jx/issues/449 for a similar issue
if strings.Contains(err.Error(), "invalid cross-device link") {
bytes, err := os.ReadFile(src)
if err != nil {
return fmt.Errorf("Could not do backup read from %s: %s\n", src, err)
}
if err := os.WriteFile(dst, bytes, 0644); err != nil {
return fmt.Errorf("Could not do backup write of %d bytes to %s: %s\n", len(bytes), dst, err)
}
// Backup "move" successful
return nil
}
return fmt.Errorf("Could not write %s -> %s: %s\n", src, dst, err)
}
return nil
}