github.com/westcoastroms/westcoastroms-build@v0.0.0-20190928114312-2350e5a73030/build/soong/cmd/pom2bp/pom2bp.go (about) 1 // Copyright 2017 Google Inc. 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 package main 16 17 import ( 18 "bufio" 19 "bytes" 20 "encoding/xml" 21 "flag" 22 "fmt" 23 "io/ioutil" 24 "os" 25 "os/exec" 26 "path/filepath" 27 "regexp" 28 "sort" 29 "strings" 30 "text/template" 31 32 "github.com/google/blueprint/proptools" 33 34 "android/soong/bpfix/bpfix" 35 ) 36 37 type RewriteNames []RewriteName 38 type RewriteName struct { 39 regexp *regexp.Regexp 40 repl string 41 } 42 43 func (r *RewriteNames) String() string { 44 return "" 45 } 46 47 func (r *RewriteNames) Set(v string) error { 48 split := strings.SplitN(v, "=", 2) 49 if len(split) != 2 { 50 return fmt.Errorf("Must be in the form of <regex>=<replace>") 51 } 52 regex, err := regexp.Compile(split[0]) 53 if err != nil { 54 return nil 55 } 56 *r = append(*r, RewriteName{ 57 regexp: regex, 58 repl: split[1], 59 }) 60 return nil 61 } 62 63 func (r *RewriteNames) MavenToBp(groupId string, artifactId string) string { 64 for _, r := range *r { 65 if r.regexp.MatchString(groupId + ":" + artifactId) { 66 return r.regexp.ReplaceAllString(groupId+":"+artifactId, r.repl) 67 } else if r.regexp.MatchString(artifactId) { 68 return r.regexp.ReplaceAllString(artifactId, r.repl) 69 } 70 } 71 return artifactId 72 } 73 74 var rewriteNames = RewriteNames{} 75 76 type ExtraDeps map[string][]string 77 78 func (d ExtraDeps) String() string { 79 return "" 80 } 81 82 func (d ExtraDeps) Set(v string) error { 83 split := strings.SplitN(v, "=", 2) 84 if len(split) != 2 { 85 return fmt.Errorf("Must be in the form of <module>=<module>[,<module>]") 86 } 87 d[split[0]] = strings.Split(split[1], ",") 88 return nil 89 } 90 91 var extraDeps = make(ExtraDeps) 92 93 type Exclude map[string]bool 94 95 func (e Exclude) String() string { 96 return "" 97 } 98 99 func (e Exclude) Set(v string) error { 100 e[v] = true 101 return nil 102 } 103 104 var excludes = make(Exclude) 105 106 var sdkVersion string 107 var useVersion string 108 109 func InList(s string, list []string) bool { 110 for _, l := range list { 111 if l == s { 112 return true 113 } 114 } 115 116 return false 117 } 118 119 type Dependency struct { 120 XMLName xml.Name `xml:"dependency"` 121 122 BpTarget string `xml:"-"` 123 124 GroupId string `xml:"groupId"` 125 ArtifactId string `xml:"artifactId"` 126 Version string `xml:"version"` 127 Type string `xml:"type"` 128 Scope string `xml:"scope"` 129 } 130 131 func (d Dependency) BpName() string { 132 if d.BpTarget == "" { 133 d.BpTarget = rewriteNames.MavenToBp(d.GroupId, d.ArtifactId) 134 } 135 return d.BpTarget 136 } 137 138 type Pom struct { 139 XMLName xml.Name `xml:"http://maven.apache.org/POM/4.0.0 project"` 140 141 PomFile string `xml:"-"` 142 ArtifactFile string `xml:"-"` 143 BpTarget string `xml:"-"` 144 145 GroupId string `xml:"groupId"` 146 ArtifactId string `xml:"artifactId"` 147 Version string `xml:"version"` 148 Packaging string `xml:"packaging"` 149 150 Dependencies []*Dependency `xml:"dependencies>dependency"` 151 } 152 153 func (p Pom) IsAar() bool { 154 return p.Packaging == "aar" 155 } 156 157 func (p Pom) IsJar() bool { 158 return p.Packaging == "jar" 159 } 160 161 func (p Pom) BpName() string { 162 if p.BpTarget == "" { 163 p.BpTarget = rewriteNames.MavenToBp(p.GroupId, p.ArtifactId) 164 } 165 return p.BpTarget 166 } 167 168 func (p Pom) BpJarDeps() []string { 169 return p.BpDeps("jar", []string{"compile", "runtime"}) 170 } 171 172 func (p Pom) BpAarDeps() []string { 173 return p.BpDeps("aar", []string{"compile", "runtime"}) 174 } 175 176 func (p Pom) BpExtraDeps() []string { 177 return extraDeps[p.BpName()] 178 } 179 180 // BpDeps obtains dependencies filtered by type and scope. The results of this 181 // method are formatted as Android.bp targets, e.g. run through MavenToBp rules. 182 func (p Pom) BpDeps(typeExt string, scopes []string) []string { 183 var ret []string 184 for _, d := range p.Dependencies { 185 if d.Type != typeExt || !InList(d.Scope, scopes) { 186 continue 187 } 188 name := rewriteNames.MavenToBp(d.GroupId, d.ArtifactId) 189 ret = append(ret, name) 190 } 191 return ret 192 } 193 194 func (p Pom) SdkVersion() string { 195 return sdkVersion 196 } 197 198 func (p *Pom) FixDeps(modules map[string]*Pom) { 199 for _, d := range p.Dependencies { 200 if d.Type == "" { 201 if depPom, ok := modules[d.BpName()]; ok { 202 // We've seen the POM for this dependency, use its packaging 203 // as the dependency type rather than Maven spec default. 204 d.Type = depPom.Packaging 205 } else { 206 // Dependency type was not specified and we don't have the POM 207 // for this artifact, use the default from Maven spec. 208 d.Type = "jar" 209 } 210 } 211 if d.Scope == "" { 212 // Scope was not specified, use the default from Maven spec. 213 d.Scope = "compile" 214 } 215 } 216 } 217 218 var bpTemplate = template.Must(template.New("bp").Parse(` 219 {{if .IsAar}}android_library_import{{else}}java_import{{end}} { 220 name: "{{.BpName}}-nodeps", 221 {{if .IsAar}}aars{{else}}jars{{end}}: ["{{.ArtifactFile}}"], 222 sdk_version: "{{.SdkVersion}}",{{if .IsAar}} 223 static_libs: [{{range .BpAarDeps}} 224 "{{.}}",{{end}}{{range .BpExtraDeps}} 225 "{{.}}",{{end}} 226 ],{{end}} 227 } 228 229 {{if .IsAar}}android_library{{else}}java_library_static{{end}} { 230 name: "{{.BpName}}", 231 sdk_version: "{{.SdkVersion}}",{{if .IsAar}} 232 manifest: "manifests/{{.BpName}}/AndroidManifest.xml",{{end}} 233 static_libs: [ 234 "{{.BpName}}-nodeps",{{range .BpJarDeps}} 235 "{{.}}",{{end}}{{range .BpAarDeps}} 236 "{{.}}",{{end}}{{range .BpExtraDeps}} 237 "{{.}}",{{end}} 238 ], 239 java_version: "1.7", 240 } 241 `)) 242 243 func parse(filename string) (*Pom, error) { 244 data, err := ioutil.ReadFile(filename) 245 if err != nil { 246 return nil, err 247 } 248 249 var pom Pom 250 err = xml.Unmarshal(data, &pom) 251 if err != nil { 252 return nil, err 253 } 254 255 if useVersion != "" && pom.Version != useVersion { 256 return nil, nil 257 } 258 259 if pom.Packaging == "" { 260 pom.Packaging = "jar" 261 } 262 263 pom.PomFile = filename 264 pom.ArtifactFile = strings.TrimSuffix(filename, ".pom") + "." + pom.Packaging 265 266 return &pom, nil 267 } 268 269 func rerunForRegen(filename string) error { 270 buf, err := ioutil.ReadFile(filename) 271 if err != nil { 272 return err 273 } 274 275 scanner := bufio.NewScanner(bytes.NewBuffer(buf)) 276 277 // Skip the first line in the file 278 for i := 0; i < 2; i++ { 279 if !scanner.Scan() { 280 if scanner.Err() != nil { 281 return scanner.Err() 282 } else { 283 return fmt.Errorf("unexpected EOF") 284 } 285 } 286 } 287 288 // Extract the old args from the file 289 line := scanner.Text() 290 if strings.HasPrefix(line, "// pom2bp ") { 291 line = strings.TrimPrefix(line, "// pom2bp ") 292 } else if strings.HasPrefix(line, "// pom2mk ") { 293 line = strings.TrimPrefix(line, "// pom2mk ") 294 } else if strings.HasPrefix(line, "# pom2mk ") { 295 line = strings.TrimPrefix(line, "# pom2mk ") 296 } else { 297 return fmt.Errorf("unexpected second line: %q", line) 298 } 299 args := strings.Split(line, " ") 300 lastArg := args[len(args)-1] 301 args = args[:len(args)-1] 302 303 // Append all current command line args except -regen <file> to the ones from the file 304 for i := 1; i < len(os.Args); i++ { 305 if os.Args[i] == "-regen" { 306 i++ 307 } else { 308 args = append(args, os.Args[i]) 309 } 310 } 311 args = append(args, lastArg) 312 313 cmd := os.Args[0] + " " + strings.Join(args, " ") 314 // Re-exec pom2bp with the new arguments 315 output, err := exec.Command("/bin/sh", "-c", cmd).Output() 316 if exitErr, _ := err.(*exec.ExitError); exitErr != nil { 317 return fmt.Errorf("failed to run %s\n%s", cmd, string(exitErr.Stderr)) 318 } else if err != nil { 319 return err 320 } 321 322 // If the old file was a .mk file, replace it with a .bp file 323 if filepath.Ext(filename) == ".mk" { 324 os.Remove(filename) 325 filename = strings.TrimSuffix(filename, ".mk") + ".bp" 326 } 327 328 return ioutil.WriteFile(filename, output, 0666) 329 } 330 331 func main() { 332 flag.Usage = func() { 333 fmt.Fprintf(os.Stderr, `pom2bp, a tool to create Android.bp files from maven repos 334 335 The tool will extract the necessary information from *.pom files to create an Android.bp whose 336 aar libraries can be linked against when using AAPT2. 337 338 Usage: %s [--rewrite <regex>=<replace>] [-exclude <module>] [--extra-deps <module>=<module>[,<module>]] [<dir>] [-regen <file>] 339 340 -rewrite <regex>=<replace> 341 rewrite can be used to specify mappings between Maven projects and Android.bp modules. The -rewrite 342 option can be specified multiple times. When determining the Android.bp module for a given Maven 343 project, mappings are searched in the order they were specified. The first <regex> matching 344 either the Maven project's <groupId>:<artifactId> or <artifactId> will be used to generate 345 the Android.bp module name using <replace>. If no matches are found, <artifactId> is used. 346 -exclude <module> 347 Don't put the specified module in the Android.bp file. 348 -extra-deps <module>=<module>[,<module>] 349 Some Android.bp modules have transitive dependencies that must be specified when they are 350 depended upon (like android-support-v7-mediarouter requires android-support-v7-appcompat). 351 This may be specified multiple times to declare these dependencies. 352 -sdk-version <version> 353 Sets LOCAL_SDK_VERSION := <version> for all modules. 354 -use-version <version> 355 If the maven directory contains multiple versions of artifacts and their pom files, 356 -use-version can be used to only write Android.bp files for a specific version of those artifacts. 357 <dir> 358 The directory to search for *.pom files under. 359 The contents are written to stdout, to be put in the current directory (often as Android.bp) 360 -regen <file> 361 Read arguments from <file> and overwrite it (if it ends with .bp) or move it to .bp (if it 362 ends with .mk). 363 364 `, os.Args[0]) 365 } 366 367 var regen string 368 369 flag.Var(&excludes, "exclude", "Exclude module") 370 flag.Var(&extraDeps, "extra-deps", "Extra dependencies needed when depending on a module") 371 flag.Var(&rewriteNames, "rewrite", "Regex(es) to rewrite artifact names") 372 flag.StringVar(&sdkVersion, "sdk-version", "", "What to write to LOCAL_SDK_VERSION") 373 flag.StringVar(&useVersion, "use-version", "", "Only read artifacts of a specific version") 374 flag.Bool("static-deps", false, "Ignored") 375 flag.StringVar(®en, "regen", "", "Rewrite specified file") 376 flag.Parse() 377 378 if regen != "" { 379 err := rerunForRegen(regen) 380 if err != nil { 381 fmt.Fprintln(os.Stderr, err) 382 os.Exit(1) 383 } 384 os.Exit(0) 385 } 386 387 if flag.NArg() == 0 { 388 fmt.Fprintln(os.Stderr, "Directory argument is required") 389 os.Exit(1) 390 } else if flag.NArg() > 1 { 391 fmt.Fprintln(os.Stderr, "Multiple directories provided:", strings.Join(flag.Args(), " ")) 392 os.Exit(1) 393 } 394 395 dir := flag.Arg(0) 396 absDir, err := filepath.Abs(dir) 397 if err != nil { 398 fmt.Fprintln(os.Stderr, "Failed to get absolute directory:", err) 399 os.Exit(1) 400 } 401 402 var filenames []string 403 err = filepath.Walk(absDir, func(path string, info os.FileInfo, err error) error { 404 if err != nil { 405 return err 406 } 407 408 name := info.Name() 409 if info.IsDir() { 410 if strings.HasPrefix(name, ".") { 411 return filepath.SkipDir 412 } 413 return nil 414 } 415 416 if strings.HasPrefix(name, ".") { 417 return nil 418 } 419 420 if strings.HasSuffix(name, ".pom") { 421 path, err = filepath.Rel(absDir, path) 422 if err != nil { 423 return err 424 } 425 filenames = append(filenames, filepath.Join(dir, path)) 426 } 427 return nil 428 }) 429 if err != nil { 430 fmt.Fprintln(os.Stderr, "Error walking files:", err) 431 os.Exit(1) 432 } 433 434 if len(filenames) == 0 { 435 fmt.Fprintln(os.Stderr, "Error: no *.pom files found under", dir) 436 os.Exit(1) 437 } 438 439 sort.Strings(filenames) 440 441 poms := []*Pom{} 442 modules := make(map[string]*Pom) 443 duplicate := false 444 for _, filename := range filenames { 445 pom, err := parse(filename) 446 if err != nil { 447 fmt.Fprintln(os.Stderr, "Error converting", filename, err) 448 os.Exit(1) 449 } 450 451 if pom != nil { 452 key := pom.BpName() 453 if excludes[key] { 454 continue 455 } 456 457 if old, ok := modules[key]; ok { 458 fmt.Fprintln(os.Stderr, "Module", key, "defined twice:", old.PomFile, pom.PomFile) 459 duplicate = true 460 } 461 462 poms = append(poms, pom) 463 modules[key] = pom 464 } 465 } 466 if duplicate { 467 os.Exit(1) 468 } 469 470 for _, pom := range poms { 471 pom.FixDeps(modules) 472 } 473 474 buf := &bytes.Buffer{} 475 476 fmt.Fprintln(buf, "// Automatically generated with:") 477 fmt.Fprintln(buf, "// pom2bp", strings.Join(proptools.ShellEscape(os.Args[1:]), " ")) 478 479 for _, pom := range poms { 480 var err error 481 err = bpTemplate.Execute(buf, pom) 482 if err != nil { 483 fmt.Fprintln(os.Stderr, "Error writing", pom.PomFile, pom.BpName(), err) 484 os.Exit(1) 485 } 486 } 487 488 out, err := bpfix.Reformat(buf.String()) 489 if err != nil { 490 fmt.Fprintln(os.Stderr, "Error formatting output", err) 491 os.Exit(1) 492 } 493 494 os.Stdout.WriteString(out) 495 }