kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/go/extractors/gcp/config/converter.go (about) 1 /* 2 * Copyright 2018 The Kythe Authors. All rights reserved. 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 17 // Package config contains logic for converting 18 // kythe.proto.extraction.RepoConfig to cloudbuild.yaml format as specified by 19 // https://cloud.google.com/cloud-build/docs/build-config. 20 package config // import "kythe.io/kythe/go/extractors/gcp/config" 21 22 import ( 23 "fmt" 24 "io/ioutil" 25 "os" 26 "path" 27 "strconv" 28 "strings" 29 30 rpb "kythe.io/kythe/proto/repo_go_proto" 31 32 "google.golang.org/api/cloudbuild/v1" 33 "google.golang.org/protobuf/encoding/protojson" 34 "sigs.k8s.io/yaml" 35 ) 36 37 // Constants that map input/output substitutions. 38 const ( 39 defaultCorpus = "${_CORPUS}" 40 defaultVersion = "${_COMMIT}" 41 outputGsBucket = "${_BUCKET_NAME}" 42 defaultRepoName = "${_REPO}" 43 ) 44 45 // Constants ephemeral to a single kythe cloudbuild run. 46 const ( 47 outputDirectory = "/workspace/output" 48 codeDirectory = "/workspace/code" 49 javaVolumeName = "kythe_extractors" 50 ) 51 52 // KytheToYAML takes an input JSON file defined in 53 // kythe.proto.extraction.RepoConfig format, and returns it as marshalled YAML. 54 func KytheToYAML(input string) ([]byte, error) { 55 configProto, err := readConfigFile(input) 56 if err != nil { 57 return nil, fmt.Errorf("reading config file %s: %v", input, configProto) 58 } 59 build, err := KytheToBuild(configProto) 60 if err != nil { 61 return nil, fmt.Errorf("converting cloudbuild.Build: %v", err) 62 } 63 json, err := build.MarshalJSON() 64 if err != nil { 65 return nil, fmt.Errorf("marshalling cloudbuild.Build to JSON: %v", err) 66 } 67 return yaml.JSONToYAML(json) 68 } 69 70 // KytheToBuild takes a kythe.proto.extraction.RepoConfig and returns the 71 // necessary cloudbuild.Build with BuildSteps for running kythe extraction. 72 func KytheToBuild(conf *rpb.Config) (*cloudbuild.Build, error) { 73 if len(conf.Extractions) == 0 { 74 return nil, fmt.Errorf("config has no extraction specified") 75 } else if len(conf.Extractions) > 1 { 76 return nil, fmt.Errorf("we don't yet support multiple corpus extraction yet") 77 } 78 79 repo := conf.Repo 80 if repo == "" { 81 // Default to a $_{REPO} string replacement. 82 // TODO(danielmoy): this kludge should be removed in favor of having 83 // config generation be a separate process (something should generate a 84 // qualified rpb.Config object, obviating the need to fall back to the 85 // $_{VAR} replacements. 86 repo = defaultRepoName 87 } 88 89 hints := conf.Extractions[0] 90 if hints.Corpus == "" { 91 // Default to a $_{CORPUS} string replacement. 92 hints.Corpus = defaultCorpus 93 } 94 95 build := &cloudbuild.Build{ 96 Artifacts: &cloudbuild.Artifacts{ 97 Objects: &cloudbuild.ArtifactObjects{ 98 Location: fmt.Sprintf("gs://%s/%s/", outputGsBucket, hints.Corpus), 99 Paths: []string{path.Join(outputDirectory, outputFileName())}, 100 }, 101 }, 102 // TODO(danielmoy): this should probably also be a generator, or at least 103 // if there is refactoring work done as described below to make steps 104 // more granular, this will have to hook into that logic. 105 Steps: commonSteps(repo), 106 Tags: []string{hints.Corpus}, 107 } 108 109 g, err := generator(hints.BuildSystem) 110 if err != nil { 111 return nil, err 112 } 113 build.Tags = append(build.Tags, "kythe_extract_"+strings.ToLower(hints.BuildSystem.String())) 114 115 build.Steps = append(build.Steps, g.preExtractSteps()...) 116 117 targets := hints.Targets 118 if len(targets) == 0 { 119 targets = append(targets, g.defaultExtractionTarget()) 120 } 121 for i, target := range targets { 122 idSuffix := "" 123 if len(targets) > 1 { 124 idSuffix = strconv.Itoa(i) 125 } 126 build.Steps = append(build.Steps, g.extractSteps(hints.Corpus, target, idSuffix)...) 127 } 128 129 build.Steps = append(build.Steps, g.postExtractSteps(hints.Corpus)...) 130 131 return build, nil 132 } 133 134 func readConfigFile(input string) (*rpb.Config, error) { 135 conf := &rpb.Config{} 136 file, err := os.Open(input) 137 if err != nil { 138 return nil, fmt.Errorf("opening input file %s: %v", input, err) 139 } 140 defer file.Close() 141 rec, err := ioutil.ReadAll(file) 142 if err != nil { 143 return nil, fmt.Errorf("reading json file %s: %v", input, err) 144 } 145 if err := protojson.Unmarshal(rec, conf); err != nil { 146 return nil, fmt.Errorf("parsing json file %s: %v", input, err) 147 } 148 return conf, nil 149 } 150 151 // buildSystemElaborator encapsulates the logic for adding extra build steps for a 152 // specific type of build system (maven, gradle, etc). 153 // TODO(danielmoy): we will almost certainly need to support more fine-grained 154 // steps and/or artifacts. For example now artifacts are prepended, we might 155 // need ones that are appended. We might need build steps that occur before or 156 // directly after cloning, but before other common steps. 157 type buildSystemElaborator interface { 158 // preExtractSteps is a list of cloudbuild steps to be done before 159 // extraction starts, specific to this extractor type. 160 preExtractSteps() []*cloudbuild.BuildStep 161 // extractSteps is a list of cloudbuild steps to extract the given target. 162 // The idSuffix is simply a unique identifier for this instance and target, 163 // for use in coordinating paralleism if desired. 164 extractSteps(corpus string, target *rpb.ExtractionTarget, idSuffix string) []*cloudbuild.BuildStep 165 // postExtractSteps is a list of cloudbuild steps to be done after 166 // extraction finishes. 167 postExtractSteps(corpus string) []*cloudbuild.BuildStep 168 // defaultExtractionTarget is the repo-relative location of the default build 169 // configuration file for a repo of a given type. For example for a bazel 170 // repo it might just be root/BUILD, or a maven repo will have repo/pom.xml. 171 defaultExtractionTarget() *rpb.ExtractionTarget 172 } 173 174 func generator(b rpb.BuildSystem) (buildSystemElaborator, error) { 175 switch b { 176 case rpb.BuildSystem_MAVEN: 177 return &mavenGenerator{}, nil 178 case rpb.BuildSystem_GRADLE: 179 return &gradleGenerator{}, nil 180 case rpb.BuildSystem_BAZEL: 181 return &bazelGenerator{}, nil 182 default: 183 return nil, fmt.Errorf("unsupported build system %s", b) 184 } 185 } 186 187 func outputFileName() string { 188 return defaultVersion + ".kzip" 189 }