• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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(&regen, "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