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