• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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