kythe.io@v0.0.68-0.20240422202219-7225dbc01741/kythe/go/extractors/config/preprocessor/modifier/pom_xml_modifier.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 modifier 18 19 import ( 20 "fmt" 21 "os" 22 "strconv" 23 "strings" 24 25 "github.com/beevik/etree" 26 ) 27 28 // PreProcessPomXML takes a pom.xml file and either verifies that it already has 29 // the bits necessary to specify a separate compiler on commandline, or adds 30 // functionality by dropping in a maven-compiler-plugin to the build. 31 // 32 // Note this potentially overwrites the input file, even if it returns an error, 33 // so make a copy beforehand if you need to keep the original. 34 func PreProcessPomXML(pomXMLFile string) error { 35 doc := etree.NewDocument() 36 err := doc.ReadFromFile(pomXMLFile) 37 if err != nil { 38 return fmt.Errorf("reading XML file %s: %v", pomXMLFile, err) 39 } 40 hasPlugin, err := hasCompilerPlugin(doc) 41 if err != nil { 42 return err 43 } 44 if hasPlugin { 45 return nil 46 } 47 if err := insertCompilerPlugin(doc); err != nil { 48 return fmt.Errorf("adding maven-compiler-plugin: %v", err) 49 } 50 f, err := os.OpenFile(pomXMLFile, os.O_WRONLY|os.O_CREATE|os.O_TRUNC, 0644) 51 if err != nil { 52 return fmt.Errorf("opening file: %v", err) 53 } 54 doc.Indent(2) 55 doc.WriteTo(f) 56 return f.Close() 57 } 58 59 // hasCompilerPLugin returns whether there is a maven-compiler-plugin specified 60 // already. Additionally return whether or not we optimistically think that any 61 // specified maven-compiler-plugin will work for us, or if we can see obvious 62 // errors. 63 func hasCompilerPlugin(doc *etree.Document) (bool, error) { 64 for _, p := range doc.FindElements("//project/build/plugins/plugin") { 65 a := p.FindElement("artifactId") 66 if a.Text() == "maven-compiler-plugin" { 67 // Now, we either check for the existence of correctly-specified 68 // elements for groupId, version, and 69 // configuration/[source|target], or we check for the complete 70 // absence of all those elements. For the former we check 71 // validity, and for the latter we just assume that there's some 72 // well-formed parent pluginManagement specifying the values. 73 if len(p.ChildElements()) == 1 { 74 // Assume that there's some well-formed parent 75 // pluginManagement specifying the values. 76 // TODO(danielmoy): handle the case where pom.xml plugin is 77 // incorrectly specified, by looking at all of the pom.xml 78 // files in the whole project wholistically. This might be 79 // out of scope - we might insist they be fixed upstream. 80 return true, nil 81 } 82 return true, wellformedCompilerPlugin(p) 83 } 84 } 85 for _, p := range doc.FindElements("//project/build/pluginManagement/plugins/plugin") { 86 a := p.FindElement("artifactId") 87 if a.Text() == "maven-compiler-plugin" { 88 // For pluginManagement, we do need a well-specified set of 89 // sibling elements. 90 return true, wellformedCompilerPlugin(p) 91 } 92 } 93 94 // If we didn't find anything, we can proceed with modification. 95 return false, nil 96 } 97 98 // wellformedCompilerPlugin checks to see if the plugin fits the minimum 99 // supported versions we've tested: 100 // <plugin> 101 // 102 // <artifactId>maven-compiler-plugin</artifactId> 103 // <version>3.7.0</version> 104 // <configuration> 105 // <source>1.8</source> 106 // <target>1.8</target> 107 // </configuration> 108 // 109 // </plugin> 110 // TODO(danielmoy): see if we can relax some of these restrictions. 111 func wellformedCompilerPlugin(el *etree.Element) error { 112 v := el.FindElement("version") 113 if v == nil { 114 return fmt.Errorf("missing maven-compiler-plugin version") 115 } 116 vt, err := semvar(v.Text()) 117 if err != nil || vt.earlierThan(versiontuple{3, 7, 0}) { 118 return fmt.Errorf("unsupported maven-compiler-plugin version: %s, need 3.7.0+", v.Text()) 119 } 120 121 if err := checkJavaVersion(el, "source"); err != nil { 122 return err 123 } 124 if err := checkJavaVersion(el, "target"); err != nil { 125 return err 126 } 127 128 return nil 129 } 130 131 func checkJavaVersion(el *etree.Element, kind string) error { 132 k := el.FindElement(fmt.Sprintf("configuration/%s", kind)) 133 if k == nil { 134 return fmt.Errorf("mising %s", kind) 135 } 136 if v, err := javaVersion(k.Text()); err != nil || v.earlierThan(versiontuple{1, 7}) { 137 // TODO(#3075): Should test an upper bound here too. 138 return fmt.Errorf("unsupported %s version: %s, need 1.7+", kind, k.Text()) 139 } 140 return nil 141 } 142 143 // Some container that supports semvar (3.7.0) and java versions (1.6) for 144 // comparison purposes only. 145 type versiontuple []int 146 147 func semvar(st string) (versiontuple, error) { 148 return fromString(st, 3) 149 } 150 151 func javaVersion(st string) (versiontuple, error) { 152 return fromString(st, 2) 153 } 154 155 func fromString(st string, size int) (versiontuple, error) { 156 v := versiontuple{} 157 bits := strings.Split(st, ".") 158 if len(bits) != size { 159 return v, fmt.Errorf("version should have %d parts separated by '.', got: %s", size, st) 160 } 161 for i := 0; i < size; i++ { 162 id, err := strconv.Atoi(bits[i]) 163 if err != nil { 164 return v, fmt.Errorf("version %s has malformed index %d: %v", st, i, err) 165 } 166 v = append(v, id) 167 } 168 return v, nil 169 } 170 171 func (vt versiontuple) earlierThan(other versiontuple) bool { 172 // Version tuples aren't of the same length, probably shouldn't be 173 // compared 174 if len(vt) != len(other) { 175 return false 176 } 177 for i := 0; i < len(vt); i++ { 178 if vt[i] < other[i] { 179 return true 180 } else if vt[i] > other[i] { 181 return false 182 } 183 } 184 return false 185 } 186 187 // appendCompilerPlugin modifies a doc containing the root element of a pom.xml 188 // configuration, by injecting a custom Maven compiler <plugin> element into the 189 // plugin list for the top-level project. This allows mvn install commands to 190 // specify --Dmaven.compiler.executable on commandline and use a separate 191 // compiler. 192 // 193 // Note this is unlike the build.gradle modification, where a separate 194 // commandline argument is not necessary. 195 // 196 // An example modification can be found in the tests. We would expect 197 // testdata/other-pom.xml to be transformed into testdata/modified-pom.xml 198 func insertCompilerPlugin(doc *etree.Document) error { 199 project := doc.SelectElement("project") 200 if project == nil { 201 return fmt.Errorf("no top level <project> element") 202 } 203 build := project.SelectElement("build") 204 if build == nil { 205 build = project.CreateElement("build") 206 } 207 plugins := build.SelectElement("plugins") 208 if plugins == nil { 209 plugins = build.CreateElement("plugins") 210 } 211 newPlugin := plugins.CreateElement("plugin") 212 groupID := newPlugin.CreateElement("groupId") 213 groupID.SetText("org.apache.maven.plugins") 214 artifactID := newPlugin.CreateElement("artifactId") 215 artifactID.SetText("maven-compiler-plugin") 216 version := newPlugin.CreateElement("version") 217 version.SetText("3.7.0") 218 configuration := newPlugin.CreateElement("configuration") 219 source := configuration.CreateElement("source") 220 source.SetText("1.8") 221 target := configuration.CreateElement("target") 222 target.SetText("1.8") 223 return nil 224 }