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 init() { 35 if os.Getenv("BUILD_WORKSPACE_DIRECTORY") == "" { 36 log.Fatalln("ERROR: this program must run under Bazel") 37 } 38} 39 40func main() { 41 var ( 42 manifestGeneratorHashPath string 43 requirementsPath string 44 pipRepositoryName string 45 usePipRepositoryAliases bool 46 modulesMappingPath string 47 outputPath string 48 updateTarget string 49 ) 50 flag.StringVar( 51 &manifestGeneratorHashPath, 52 "manifest-generator-hash", 53 "", 54 "The file containing the hash for the source code of the manifest generator."+ 55 "This is important to force manifest updates when the generator logic changes.") 56 flag.StringVar( 57 &requirementsPath, 58 "requirements", 59 "", 60 "The requirements.txt file.") 61 flag.StringVar( 62 &pipRepositoryName, 63 "pip-repository-name", 64 "", 65 "The name of the pip_install or pip_repository target.") 66 flag.BoolVar( 67 &usePipRepositoryAliases, 68 "use-pip-repository-aliases", 69 false, 70 "Whether to use the pip-repository aliases, which are generated when passing 'incompatible_generate_aliases = True'.") 71 flag.StringVar( 72 &modulesMappingPath, 73 "modules-mapping", 74 "", 75 "The modules_mapping.json file.") 76 flag.StringVar( 77 &outputPath, 78 "output", 79 "", 80 "The output YAML manifest file.") 81 flag.StringVar( 82 &updateTarget, 83 "update-target", 84 "", 85 "The Bazel target to update the YAML manifest file.") 86 flag.Parse() 87 88 if requirementsPath == "" { 89 log.Fatalln("ERROR: --requirements must be set") 90 } 91 92 if modulesMappingPath == "" { 93 log.Fatalln("ERROR: --modules-mapping must be set") 94 } 95 96 if outputPath == "" { 97 log.Fatalln("ERROR: --output must be set") 98 } 99 100 if updateTarget == "" { 101 log.Fatalln("ERROR: --update-target must be set") 102 } 103 104 modulesMapping, err := unmarshalJSON(modulesMappingPath) 105 if err != nil { 106 log.Fatalf("ERROR: %v\n", err) 107 } 108 109 header := generateHeader(updateTarget) 110 111 manifestFile := manifest.NewFile(&manifest.Manifest{ 112 ModulesMapping: modulesMapping, 113 PipRepository: &manifest.PipRepository{ 114 Name: pipRepositoryName, 115 UsePipRepositoryAliases: usePipRepositoryAliases, 116 }, 117 }) 118 if err := writeOutput( 119 outputPath, 120 header, 121 manifestFile, 122 manifestGeneratorHashPath, 123 requirementsPath, 124 ); err != nil { 125 log.Fatalf("ERROR: %v\n", err) 126 } 127} 128 129// unmarshalJSON returns the parsed mapping from the given JSON file path. 130func unmarshalJSON(jsonPath string) (map[string]string, error) { 131 file, err := os.Open(jsonPath) 132 if err != nil { 133 return nil, fmt.Errorf("failed to unmarshal JSON file: %w", err) 134 } 135 defer file.Close() 136 137 decoder := json.NewDecoder(file) 138 output := make(map[string]string) 139 if err := decoder.Decode(&output); err != nil { 140 return nil, fmt.Errorf("failed to unmarshal JSON file: %w", err) 141 } 142 143 return output, nil 144} 145 146// generateHeader generates the YAML header human-readable comment. 147func generateHeader(updateTarget string) string { 148 var header strings.Builder 149 header.WriteString("# GENERATED FILE - DO NOT EDIT!\n") 150 header.WriteString("#\n") 151 header.WriteString("# To update this file, run:\n") 152 header.WriteString(fmt.Sprintf("# bazel run %s\n", updateTarget)) 153 return header.String() 154} 155 156// writeOutput writes to the final file the header and manifest structure. 157func writeOutput( 158 outputPath string, 159 header string, 160 manifestFile *manifest.File, 161 manifestGeneratorHashPath string, 162 requirementsPath string, 163) error { 164 stat, err := os.Stat(outputPath) 165 if err != nil { 166 return fmt.Errorf("failed to write output: %w", err) 167 } 168 169 outputFile, err := os.OpenFile(outputPath, os.O_WRONLY|os.O_TRUNC, stat.Mode()) 170 if err != nil { 171 return fmt.Errorf("failed to write output: %w", err) 172 } 173 defer outputFile.Close() 174 175 if _, err := fmt.Fprintf(outputFile, "%s\n", header); err != nil { 176 return fmt.Errorf("failed to write output: %w", err) 177 } 178 179 manifestGeneratorHash, err := os.Open(manifestGeneratorHashPath) 180 if err != nil { 181 return fmt.Errorf("failed to write output: %w", err) 182 } 183 defer manifestGeneratorHash.Close() 184 185 requirements, err := os.Open(requirementsPath) 186 if err != nil { 187 return fmt.Errorf("failed to write output: %w", err) 188 } 189 defer requirements.Close() 190 191 if err := manifestFile.Encode(outputFile, manifestGeneratorHash, requirements); err != nil { 192 return fmt.Errorf("failed to write output: %w", err) 193 } 194 195 return nil 196} 197