1// Copyright 2023 The Bazel Authors. All rights reserved. 2// 3// Licensed under the Apache License, Version 2.0 (the "License"); 4// you may not use this file except in compliance with the License. 5// You may obtain a copy of the License at 6// 7// http://www.apache.org/licenses/LICENSE-2.0 8// 9// Unless required by applicable law or agreed to in writing, software 10// distributed under the License is distributed on an "AS IS" BASIS, 11// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 12// See the License for the specific language governing permissions and 13// limitations under the License. 14 15/* 16generate.go is a program that generates the Gazelle YAML manifest. 17 18The Gazelle manifest is a file that contains extra information required when 19generating the Bazel BUILD files. 20*/ 21package main 22 23import ( 24 "encoding/json" 25 "flag" 26 "fmt" 27 "log" 28 "os" 29 "strings" 30 31 "github.com/bazelbuild/rules_python/gazelle/manifest" 32) 33 34func main() { 35 var ( 36 manifestGeneratorHashPath string 37 requirementsPath string 38 pipRepositoryName string 39 modulesMappingPath string 40 outputPath string 41 updateTarget string 42 ) 43 flag.StringVar( 44 &manifestGeneratorHashPath, 45 "manifest-generator-hash", 46 "", 47 "The file containing the hash for the source code of the manifest generator."+ 48 "This is important to force manifest updates when the generator logic changes.") 49 flag.StringVar( 50 &requirementsPath, 51 "requirements", 52 "", 53 "The requirements.txt file.") 54 flag.StringVar( 55 &pipRepositoryName, 56 "pip-repository-name", 57 "", 58 "The name of the pip_install or pip_repository target.") 59 flag.StringVar( 60 &modulesMappingPath, 61 "modules-mapping", 62 "", 63 "The modules_mapping.json file.") 64 flag.StringVar( 65 &outputPath, 66 "output", 67 "", 68 "The output YAML manifest file.") 69 flag.StringVar( 70 &updateTarget, 71 "update-target", 72 "", 73 "The Bazel target to update the YAML manifest file.") 74 flag.Parse() 75 76 if modulesMappingPath == "" { 77 log.Fatalln("ERROR: --modules-mapping must be set") 78 } 79 80 if outputPath == "" { 81 log.Fatalln("ERROR: --output must be set") 82 } 83 84 if updateTarget == "" { 85 log.Fatalln("ERROR: --update-target must be set") 86 } 87 88 modulesMapping, err := unmarshalJSON(modulesMappingPath) 89 if err != nil { 90 log.Fatalf("ERROR: %v\n", err) 91 } 92 93 header := generateHeader(updateTarget) 94 repository := manifest.PipRepository{ 95 Name: pipRepositoryName, 96 } 97 98 manifestFile := manifest.NewFile(&manifest.Manifest{ 99 ModulesMapping: modulesMapping, 100 PipRepository: &repository, 101 }) 102 if err := writeOutput( 103 outputPath, 104 header, 105 manifestFile, 106 manifestGeneratorHashPath, 107 requirementsPath, 108 ); err != nil { 109 log.Fatalf("ERROR: %v\n", err) 110 } 111} 112 113// unmarshalJSON returns the parsed mapping from the given JSON file path. 114func unmarshalJSON(jsonPath string) (map[string]string, error) { 115 file, err := os.Open(jsonPath) 116 if err != nil { 117 return nil, fmt.Errorf("failed to unmarshal JSON file: %w", err) 118 } 119 defer file.Close() 120 121 decoder := json.NewDecoder(file) 122 output := make(map[string]string) 123 if err := decoder.Decode(&output); err != nil { 124 return nil, fmt.Errorf("failed to unmarshal JSON file: %w", err) 125 } 126 127 return output, nil 128} 129 130// generateHeader generates the YAML header human-readable comment. 131func generateHeader(updateTarget string) string { 132 var header strings.Builder 133 header.WriteString("# GENERATED FILE - DO NOT EDIT!\n") 134 header.WriteString("#\n") 135 header.WriteString("# To update this file, run:\n") 136 header.WriteString(fmt.Sprintf("# bazel run %s\n", updateTarget)) 137 return header.String() 138} 139 140// writeOutput writes to the final file the header and manifest structure. 141func writeOutput( 142 outputPath string, 143 header string, 144 manifestFile *manifest.File, 145 manifestGeneratorHashPath string, 146 requirementsPath string, 147) error { 148 outputFile, err := os.OpenFile(outputPath, os.O_WRONLY|os.O_TRUNC|os.O_CREATE, 0644) 149 if err != nil { 150 return fmt.Errorf("failed to write output: %w", err) 151 } 152 defer outputFile.Close() 153 154 if _, err := fmt.Fprintf(outputFile, "%s\n", header); err != nil { 155 return fmt.Errorf("failed to write output: %w", err) 156 } 157 158 if requirementsPath != "" { 159 manifestGeneratorHash, err := os.Open(manifestGeneratorHashPath) 160 if err != nil { 161 return fmt.Errorf("failed to write output: %w", err) 162 } 163 defer manifestGeneratorHash.Close() 164 165 requirements, err := os.Open(requirementsPath) 166 if err != nil { 167 return fmt.Errorf("failed to write output: %w", err) 168 } 169 defer requirements.Close() 170 171 if err := manifestFile.EncodeWithIntegrity(outputFile, manifestGeneratorHash, requirements); err != nil { 172 return fmt.Errorf("failed to write output: %w", err) 173 } 174 } else { 175 if err := manifestFile.EncodeWithoutIntegrity(outputFile); err != nil { 176 return fmt.Errorf("failed to write output: %w", err) 177 } 178 } 179 180 return nil 181} 182