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 15package main 16 17import ( 18 "archive/zip" 19 "bufio" 20 "bytes" 21 "encoding/xml" 22 "flag" 23 "fmt" 24 "io/ioutil" 25 "os" 26 "os/exec" 27 "path/filepath" 28 "regexp" 29 "sort" 30 "strings" 31 "text/template" 32 33 "github.com/google/blueprint/proptools" 34 35 "android/soong/bpfix/bpfix" 36) 37 38type RewriteNames []RewriteName 39type RewriteName struct { 40 regexp *regexp.Regexp 41 repl string 42} 43 44func (r *RewriteNames) String() string { 45 return "" 46} 47 48func (r *RewriteNames) Set(v string) error { 49 split := strings.SplitN(v, "=", 2) 50 if len(split) != 2 { 51 return fmt.Errorf("Must be in the form of <regex>=<replace>") 52 } 53 regex, err := regexp.Compile(split[0]) 54 if err != nil { 55 return nil 56 } 57 *r = append(*r, RewriteName{ 58 regexp: regex, 59 repl: split[1], 60 }) 61 return nil 62} 63 64func (r *RewriteNames) MavenToBp(groupId string, artifactId string) string { 65 for _, r := range *r { 66 if r.regexp.MatchString(groupId + ":" + artifactId) { 67 return r.regexp.ReplaceAllString(groupId+":"+artifactId, r.repl) 68 } else if r.regexp.MatchString(artifactId) { 69 return r.regexp.ReplaceAllString(artifactId, r.repl) 70 } 71 } 72 return artifactId 73} 74 75var rewriteNames = RewriteNames{} 76 77type ExtraDeps map[string][]string 78 79func (d ExtraDeps) String() string { 80 return "" 81} 82 83func (d ExtraDeps) Set(v string) error { 84 split := strings.SplitN(v, "=", 2) 85 if len(split) != 2 { 86 return fmt.Errorf("Must be in the form of <module>=<module>[,<module>]") 87 } 88 d[split[0]] = strings.Split(split[1], ",") 89 return nil 90} 91 92var extraStaticLibs = make(ExtraDeps) 93 94var extraLibs = make(ExtraDeps) 95 96type Exclude map[string]bool 97 98func (e Exclude) String() string { 99 return "" 100} 101 102func (e Exclude) Set(v string) error { 103 e[v] = true 104 return nil 105} 106 107var excludes = make(Exclude) 108 109type HostModuleNames map[string]bool 110 111func (n HostModuleNames) IsHostModule(groupId string, artifactId string) bool { 112 _, found := n[groupId+":"+artifactId] 113 return found 114} 115 116func (n HostModuleNames) String() string { 117 return "" 118} 119 120func (n HostModuleNames) Set(v string) error { 121 n[v] = true 122 return nil 123} 124 125var hostModuleNames = HostModuleNames{} 126 127type HostAndDeviceModuleNames map[string]bool 128 129func (n HostAndDeviceModuleNames) IsHostAndDeviceModule(groupId string, artifactId string) bool { 130 _, found := n[groupId+":"+artifactId] 131 132 return found 133} 134 135func (n HostAndDeviceModuleNames) String() string { 136 return "" 137} 138 139func (n HostAndDeviceModuleNames) Set(v string) error { 140 n[v] = true 141 return nil 142} 143 144var hostAndDeviceModuleNames = HostAndDeviceModuleNames{} 145 146var sdkVersion string 147var useVersion string 148var staticDeps bool 149var jetifier bool 150 151func InList(s string, list []string) bool { 152 for _, l := range list { 153 if l == s { 154 return true 155 } 156 } 157 158 return false 159} 160 161type Dependency struct { 162 XMLName xml.Name `xml:"dependency"` 163 164 BpTarget string `xml:"-"` 165 166 GroupId string `xml:"groupId"` 167 ArtifactId string `xml:"artifactId"` 168 Version string `xml:"version"` 169 Type string `xml:"type"` 170 Scope string `xml:"scope"` 171} 172 173func (d Dependency) BpName() string { 174 if d.BpTarget == "" { 175 d.BpTarget = rewriteNames.MavenToBp(d.GroupId, d.ArtifactId) 176 } 177 return d.BpTarget 178} 179 180type Pom struct { 181 XMLName xml.Name `xml:"http://maven.apache.org/POM/4.0.0 project"` 182 183 PomFile string `xml:"-"` 184 ArtifactFile string `xml:"-"` 185 BpTarget string `xml:"-"` 186 MinSdkVersion string `xml:"-"` 187 188 GroupId string `xml:"groupId"` 189 ArtifactId string `xml:"artifactId"` 190 Version string `xml:"version"` 191 Packaging string `xml:"packaging"` 192 193 Dependencies []*Dependency `xml:"dependencies>dependency"` 194} 195 196func (p Pom) IsAar() bool { 197 return p.Packaging == "aar" 198} 199 200func (p Pom) IsJar() bool { 201 return p.Packaging == "jar" 202} 203 204func (p Pom) IsHostModule() bool { 205 return hostModuleNames.IsHostModule(p.GroupId, p.ArtifactId) 206} 207 208func (p Pom) IsDeviceModule() bool { 209 return !p.IsHostModule() 210} 211 212func (p Pom) IsHostAndDeviceModule() bool { 213 return hostAndDeviceModuleNames.IsHostAndDeviceModule(p.GroupId, p.ArtifactId) 214} 215 216func (p Pom) IsHostOnly() bool { 217 return p.IsHostModule() && !p.IsHostAndDeviceModule() 218} 219 220func (p Pom) ModuleType() string { 221 if p.IsAar() { 222 return "android_library" 223 } else if p.IsHostOnly() { 224 return "java_library_host" 225 } else { 226 return "java_library_static" 227 } 228} 229 230func (p Pom) ImportModuleType() string { 231 if p.IsAar() { 232 return "android_library_import" 233 } else if p.IsHostOnly() { 234 return "java_import_host" 235 } else { 236 return "java_import" 237 } 238} 239 240func (p Pom) ImportProperty() string { 241 if p.IsAar() { 242 return "aars" 243 } else { 244 return "jars" 245 } 246} 247 248func (p Pom) BpName() string { 249 if p.BpTarget == "" { 250 p.BpTarget = rewriteNames.MavenToBp(p.GroupId, p.ArtifactId) 251 } 252 return p.BpTarget 253} 254 255func (p Pom) BpJarDeps() []string { 256 return p.BpDeps("jar", []string{"compile", "runtime"}) 257} 258 259func (p Pom) BpAarDeps() []string { 260 return p.BpDeps("aar", []string{"compile", "runtime"}) 261} 262 263func (p Pom) BpExtraStaticLibs() []string { 264 return extraStaticLibs[p.BpName()] 265} 266 267func (p Pom) BpExtraLibs() []string { 268 return extraLibs[p.BpName()] 269} 270 271// BpDeps obtains dependencies filtered by type and scope. The results of this 272// method are formatted as Android.bp targets, e.g. run through MavenToBp rules. 273func (p Pom) BpDeps(typeExt string, scopes []string) []string { 274 var ret []string 275 for _, d := range p.Dependencies { 276 if d.Type != typeExt || !InList(d.Scope, scopes) { 277 continue 278 } 279 name := rewriteNames.MavenToBp(d.GroupId, d.ArtifactId) 280 ret = append(ret, name) 281 } 282 return ret 283} 284 285func (p Pom) SdkVersion() string { 286 return sdkVersion 287} 288 289func (p Pom) Jetifier() bool { 290 return jetifier 291} 292 293func (p *Pom) FixDeps(modules map[string]*Pom) { 294 for _, d := range p.Dependencies { 295 if d.Type == "" { 296 if depPom, ok := modules[d.BpName()]; ok { 297 // We've seen the POM for this dependency, use its packaging 298 // as the dependency type rather than Maven spec default. 299 d.Type = depPom.Packaging 300 } else { 301 // Dependency type was not specified and we don't have the POM 302 // for this artifact, use the default from Maven spec. 303 d.Type = "jar" 304 } 305 } 306 if d.Scope == "" { 307 // Scope was not specified, use the default from Maven spec. 308 d.Scope = "compile" 309 } 310 } 311} 312 313// ExtractMinSdkVersion extracts the minSdkVersion from the AndroidManifest.xml file inside an aar file, or sets it 314// to "current" if it is not present. 315func (p *Pom) ExtractMinSdkVersion() error { 316 aar, err := zip.OpenReader(p.ArtifactFile) 317 if err != nil { 318 return err 319 } 320 defer aar.Close() 321 322 var manifest *zip.File 323 for _, f := range aar.File { 324 if f.Name == "AndroidManifest.xml" { 325 manifest = f 326 break 327 } 328 } 329 330 if manifest == nil { 331 return fmt.Errorf("failed to find AndroidManifest.xml in %s", p.ArtifactFile) 332 } 333 334 r, err := manifest.Open() 335 if err != nil { 336 return err 337 } 338 defer r.Close() 339 340 decoder := xml.NewDecoder(r) 341 342 manifestData := struct { 343 XMLName xml.Name `xml:"manifest"` 344 Uses_sdk struct { 345 MinSdkVersion string `xml:"http://schemas.android.com/apk/res/android minSdkVersion,attr"` 346 } `xml:"uses-sdk"` 347 }{} 348 349 err = decoder.Decode(&manifestData) 350 if err != nil { 351 return err 352 } 353 354 p.MinSdkVersion = manifestData.Uses_sdk.MinSdkVersion 355 if p.MinSdkVersion == "" { 356 p.MinSdkVersion = "current" 357 } 358 359 return nil 360} 361 362var bpTemplate = template.Must(template.New("bp").Parse(` 363{{.ImportModuleType}} { 364 name: "{{.BpName}}", 365 {{.ImportProperty}}: ["{{.ArtifactFile}}"], 366 sdk_version: "{{.SdkVersion}}", 367 {{- if .Jetifier}} 368 jetifier: true, 369 {{- end}} 370 {{- if .IsHostAndDeviceModule}} 371 host_supported: true, 372 {{- end}} 373 {{- if not .IsHostOnly}} 374 apex_available: [ 375 "//apex_available:platform", 376 "//apex_available:anyapex", 377 ], 378 {{- end}} 379 {{- if .IsAar}} 380 min_sdk_version: "{{.MinSdkVersion}}", 381 static_libs: [ 382 {{- range .BpJarDeps}} 383 "{{.}}", 384 {{- end}} 385 {{- range .BpAarDeps}} 386 "{{.}}", 387 {{- end}} 388 {{- range .BpExtraStaticLibs}} 389 "{{.}}", 390 {{- end}} 391 ], 392 {{- if .BpExtraLibs}} 393 libs: [ 394 {{- range .BpExtraLibs}} 395 "{{.}}", 396 {{- end}} 397 ], 398 {{- end}} 399 {{- end}} 400} 401`)) 402 403var bpDepsTemplate = template.Must(template.New("bp").Parse(` 404{{.ImportModuleType}} { 405 name: "{{.BpName}}-nodeps", 406 {{.ImportProperty}}: ["{{.ArtifactFile}}"], 407 sdk_version: "{{.SdkVersion}}", 408 {{- if .Jetifier}} 409 jetifier: true, 410 {{- end}} 411 {{- if .IsHostAndDeviceModule}} 412 host_supported: true, 413 {{- end}} 414 {{- if not .IsHostOnly}} 415 apex_available: [ 416 "//apex_available:platform", 417 "//apex_available:anyapex", 418 ], 419 {{- end}} 420 {{- if .IsAar}} 421 min_sdk_version: "{{.MinSdkVersion}}", 422 static_libs: [ 423 {{- range .BpJarDeps}} 424 "{{.}}", 425 {{- end}} 426 {{- range .BpAarDeps}} 427 "{{.}}", 428 {{- end}} 429 {{- range .BpExtraStaticLibs}} 430 "{{.}}", 431 {{- end}} 432 ], 433 {{- if .BpExtraLibs}} 434 libs: [ 435 {{- range .BpExtraLibs}} 436 "{{.}}", 437 {{- end}} 438 ], 439 {{- end}} 440 {{- end}} 441} 442 443{{.ModuleType}} { 444 name: "{{.BpName}}", 445 {{- if .IsDeviceModule}} 446 sdk_version: "{{.SdkVersion}}", 447 {{- if .IsHostAndDeviceModule}} 448 host_supported: true, 449 {{- end}} 450 {{- if not .IsHostOnly}} 451 apex_available: [ 452 "//apex_available:platform", 453 "//apex_available:anyapex", 454 ], 455 {{- end}} 456 {{- if .IsAar}} 457 min_sdk_version: "{{.MinSdkVersion}}", 458 manifest: "manifests/{{.BpName}}/AndroidManifest.xml", 459 {{- else if not .IsHostOnly}} 460 min_sdk_version: "24", 461 {{- end}} 462 {{- end}} 463 static_libs: [ 464 "{{.BpName}}-nodeps", 465 {{- range .BpJarDeps}} 466 "{{.}}", 467 {{- end}} 468 {{- range .BpAarDeps}} 469 "{{.}}", 470 {{- end}} 471 {{- range .BpExtraStaticLibs}} 472 "{{.}}", 473 {{- end}} 474 ], 475 {{- if .BpExtraLibs}} 476 libs: [ 477 {{- range .BpExtraLibs}} 478 "{{.}}", 479 {{- end}} 480 ], 481 {{- end}} 482 java_version: "1.7", 483} 484`)) 485 486func parse(filename string) (*Pom, error) { 487 data, err := ioutil.ReadFile(filename) 488 if err != nil { 489 return nil, err 490 } 491 492 var pom Pom 493 err = xml.Unmarshal(data, &pom) 494 if err != nil { 495 return nil, err 496 } 497 498 if useVersion != "" && pom.Version != useVersion { 499 return nil, nil 500 } 501 502 if pom.Packaging == "" { 503 pom.Packaging = "jar" 504 } 505 506 pom.PomFile = filename 507 pom.ArtifactFile = strings.TrimSuffix(filename, ".pom") + "." + pom.Packaging 508 509 return &pom, nil 510} 511 512func rerunForRegen(filename string) error { 513 buf, err := ioutil.ReadFile(filename) 514 if err != nil { 515 return err 516 } 517 518 scanner := bufio.NewScanner(bytes.NewBuffer(buf)) 519 520 // Skip the first line in the file 521 for i := 0; i < 2; i++ { 522 if !scanner.Scan() { 523 if scanner.Err() != nil { 524 return scanner.Err() 525 } else { 526 return fmt.Errorf("unexpected EOF") 527 } 528 } 529 } 530 531 // Extract the old args from the file 532 line := scanner.Text() 533 if strings.HasPrefix(line, "// pom2bp ") { 534 line = strings.TrimPrefix(line, "// pom2bp ") 535 } else if strings.HasPrefix(line, "// pom2mk ") { 536 line = strings.TrimPrefix(line, "// pom2mk ") 537 } else if strings.HasPrefix(line, "# pom2mk ") { 538 line = strings.TrimPrefix(line, "# pom2mk ") 539 } else { 540 return fmt.Errorf("unexpected second line: %q", line) 541 } 542 args := strings.Split(line, " ") 543 lastArg := args[len(args)-1] 544 args = args[:len(args)-1] 545 546 // Append all current command line args except -regen <file> to the ones from the file 547 for i := 1; i < len(os.Args); i++ { 548 if os.Args[i] == "-regen" || os.Args[i] == "--regen" { 549 i++ 550 } else { 551 args = append(args, os.Args[i]) 552 } 553 } 554 args = append(args, lastArg) 555 556 cmd := os.Args[0] + " " + strings.Join(args, " ") 557 // Re-exec pom2bp with the new arguments 558 output, err := exec.Command("/bin/sh", "-c", cmd).Output() 559 if exitErr, _ := err.(*exec.ExitError); exitErr != nil { 560 return fmt.Errorf("failed to run %s\n%s", cmd, string(exitErr.Stderr)) 561 } else if err != nil { 562 return err 563 } 564 565 // If the old file was a .mk file, replace it with a .bp file 566 if filepath.Ext(filename) == ".mk" { 567 os.Remove(filename) 568 filename = strings.TrimSuffix(filename, ".mk") + ".bp" 569 } 570 571 return ioutil.WriteFile(filename, output, 0666) 572} 573 574func main() { 575 flag.Usage = func() { 576 fmt.Fprintf(os.Stderr, `pom2bp, a tool to create Android.bp files from maven repos 577 578The tool will extract the necessary information from *.pom files to create an Android.bp whose 579aar libraries can be linked against when using AAPT2. 580 581Usage: %s [--rewrite <regex>=<replace>] [-exclude <module>] [--extra-static-libs <module>=<module>[,<module>]] [--extra-libs <module>=<module>[,<module>]] [<dir>] [-regen <file>] 582 583 -rewrite <regex>=<replace> 584 rewrite can be used to specify mappings between Maven projects and Android.bp modules. The -rewrite 585 option can be specified multiple times. When determining the Android.bp module for a given Maven 586 project, mappings are searched in the order they were specified. The first <regex> matching 587 either the Maven project's <groupId>:<artifactId> or <artifactId> will be used to generate 588 the Android.bp module name using <replace>. If no matches are found, <artifactId> is used. 589 -exclude <module> 590 Don't put the specified module in the Android.bp file. 591 -extra-static-libs <module>=<module>[,<module>] 592 Some Android.bp modules have transitive static dependencies that must be specified when they 593 are depended upon (like android-support-v7-mediarouter requires android-support-v7-appcompat). 594 This may be specified multiple times to declare these dependencies. 595 -extra-libs <module>=<module>[,<module>] 596 Some Android.bp modules have transitive runtime dependencies that must be specified when they 597 are depended upon (like androidx.test.rules requires android.test.base). 598 This may be specified multiple times to declare these dependencies. 599 -sdk-version <version> 600 Sets sdk_version: "<version>" for all modules. 601 -use-version <version> 602 If the maven directory contains multiple versions of artifacts and their pom files, 603 -use-version can be used to only write Android.bp files for a specific version of those artifacts. 604 -jetifier 605 Sets jetifier: true for all modules. 606 <dir> 607 The directory to search for *.pom files under. 608 The contents are written to stdout, to be put in the current directory (often as Android.bp) 609 -regen <file> 610 Read arguments from <file> and overwrite it (if it ends with .bp) or move it to .bp (if it 611 ends with .mk). 612 613`, os.Args[0]) 614 } 615 616 var regen string 617 618 flag.Var(&excludes, "exclude", "Exclude module") 619 flag.Var(&extraStaticLibs, "extra-static-libs", "Extra static dependencies needed when depending on a module") 620 flag.Var(&extraLibs, "extra-libs", "Extra runtime dependencies needed when depending on a module") 621 flag.Var(&rewriteNames, "rewrite", "Regex(es) to rewrite artifact names") 622 flag.Var(&hostModuleNames, "host", "Specifies that the corresponding module (specified in the form 'module.group:module.artifact') is a host module") 623 flag.Var(&hostAndDeviceModuleNames, "host-and-device", "Specifies that the corresponding module (specified in the form 'module.group:module.artifact') is both a host and device module.") 624 flag.StringVar(&sdkVersion, "sdk-version", "", "What to write to sdk_version") 625 flag.StringVar(&useVersion, "use-version", "", "Only read artifacts of a specific version") 626 flag.BoolVar(&staticDeps, "static-deps", false, "Statically include direct dependencies") 627 flag.BoolVar(&jetifier, "jetifier", false, "Sets jetifier: true on all modules") 628 flag.StringVar(®en, "regen", "", "Rewrite specified file") 629 flag.Parse() 630 631 if regen != "" { 632 err := rerunForRegen(regen) 633 if err != nil { 634 fmt.Fprintln(os.Stderr, err) 635 os.Exit(1) 636 } 637 os.Exit(0) 638 } 639 640 if flag.NArg() == 0 { 641 fmt.Fprintln(os.Stderr, "Directory argument is required") 642 os.Exit(1) 643 } else if flag.NArg() > 1 { 644 fmt.Fprintln(os.Stderr, "Multiple directories provided:", strings.Join(flag.Args(), " ")) 645 os.Exit(1) 646 } 647 648 dir := flag.Arg(0) 649 absDir, err := filepath.Abs(dir) 650 if err != nil { 651 fmt.Fprintln(os.Stderr, "Failed to get absolute directory:", err) 652 os.Exit(1) 653 } 654 655 var filenames []string 656 err = filepath.Walk(absDir, func(path string, info os.FileInfo, err error) error { 657 if err != nil { 658 return err 659 } 660 661 name := info.Name() 662 if info.IsDir() { 663 if strings.HasPrefix(name, ".") { 664 return filepath.SkipDir 665 } 666 return nil 667 } 668 669 if strings.HasPrefix(name, ".") { 670 return nil 671 } 672 673 if strings.HasSuffix(name, ".pom") { 674 path, err = filepath.Rel(absDir, path) 675 if err != nil { 676 return err 677 } 678 filenames = append(filenames, filepath.Join(dir, path)) 679 } 680 return nil 681 }) 682 if err != nil { 683 fmt.Fprintln(os.Stderr, "Error walking files:", err) 684 os.Exit(1) 685 } 686 687 if len(filenames) == 0 { 688 fmt.Fprintln(os.Stderr, "Error: no *.pom files found under", dir) 689 os.Exit(1) 690 } 691 692 sort.Strings(filenames) 693 694 poms := []*Pom{} 695 modules := make(map[string]*Pom) 696 duplicate := false 697 for _, filename := range filenames { 698 pom, err := parse(filename) 699 if err != nil { 700 fmt.Fprintln(os.Stderr, "Error converting", filename, err) 701 os.Exit(1) 702 } 703 704 if pom != nil { 705 key := pom.BpName() 706 if excludes[key] { 707 continue 708 } 709 710 if old, ok := modules[key]; ok { 711 fmt.Fprintln(os.Stderr, "Module", key, "defined twice:", old.PomFile, pom.PomFile) 712 duplicate = true 713 } 714 715 poms = append(poms, pom) 716 modules[key] = pom 717 } 718 } 719 if duplicate { 720 os.Exit(1) 721 } 722 723 for _, pom := range poms { 724 if pom.IsAar() { 725 err := pom.ExtractMinSdkVersion() 726 if err != nil { 727 fmt.Fprintf(os.Stderr, "Error reading manifest for %s: %s", pom.ArtifactFile, err) 728 os.Exit(1) 729 } 730 } 731 pom.FixDeps(modules) 732 } 733 734 buf := &bytes.Buffer{} 735 736 fmt.Fprintln(buf, "// Automatically generated with:") 737 fmt.Fprintln(buf, "// pom2bp", strings.Join(proptools.ShellEscapeList(os.Args[1:]), " ")) 738 739 for _, pom := range poms { 740 var err error 741 if staticDeps { 742 err = bpDepsTemplate.Execute(buf, pom) 743 } else { 744 err = bpTemplate.Execute(buf, pom) 745 } 746 if err != nil { 747 fmt.Fprintln(os.Stderr, "Error writing", pom.PomFile, pom.BpName(), err) 748 os.Exit(1) 749 } 750 } 751 752 out, err := bpfix.Reformat(buf.String()) 753 if err != nil { 754 fmt.Fprintln(os.Stderr, "Error formatting output", err) 755 os.Exit(1) 756 } 757 758 os.Stdout.WriteString(out) 759} 760