xref: /aosp_15_r20/external/bazelbuild-rules_python/gazelle/manifest/generate/generate.go (revision 60517a1edbc8ecf509223e9af94a7adec7d736b8)
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