// 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 }