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