1// Copyright 2018 The Bazel Authors. 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 compile is a thin wrapper around aapt2 to compile android resources. 16package compile 17 18import ( 19 "flag" 20 "io/ioutil" 21 "log" 22 "os" 23 "os/exec" 24 "path/filepath" 25 "strings" 26 "sync" 27 28 "src/common/golang/ziputils" 29 "src/tools/ak/types" 30) 31 32var ( 33 // Cmd defines the command to run compile 34 Cmd = types.Command{ 35 Init: Init, 36 Run: Run, 37 Desc: desc, 38 Flags: []string{ 39 "aapt2", 40 "in", 41 "out", 42 }, 43 } 44 45 in string 46 aapt2 string 47 out string 48 49 initOnce sync.Once 50 51 dirPerm os.FileMode = 0755 52 dirReplacer = strings.NewReplacer("sr-rLatn", "b+sr+Latn", "es-419", "b+es+419") 53 archiveSuffix = ".zip" 54) 55 56// Init initializes compile. 57func Init() { 58 initOnce.Do(func() { 59 flag.StringVar(&aapt2, "aapt2", "", "Path to the aapt2 binary.") 60 flag.StringVar(&in, "in", "", "Input res bucket/dir to compile.") 61 flag.StringVar(&out, "out", "", "The compiled resource archive.") 62 }) 63} 64 65func desc() string { 66 return "Compile android resources directory." 67} 68 69// Run is the entry point for compile. 70func Run() { 71 if in == "" || aapt2 == "" || out == "" { 72 log.Fatal("Flags -in and -aapt2 and -out must be specified.") 73 } 74 75 fi, err := os.Stat(in) 76 if err != nil { 77 log.Fatal(err) 78 } 79 80 resDir := in 81 if !fi.IsDir() { 82 if strings.HasSuffix(resDir, archiveSuffix) { 83 // We are dealing with a resource archive. 84 td, err := ioutil.TempDir("", "-res") 85 if err != nil { 86 log.Fatal(err) 87 } 88 89 resDir = filepath.Join(td, "res/") 90 if err := os.MkdirAll(resDir, dirPerm); err != nil { 91 log.Fatal(err) 92 } 93 if err := ziputils.Unzip(in, td); err != nil { 94 log.Fatal(err) 95 } 96 } else { 97 // We are compiling a single file, but we need to provide dir. 98 resDir = filepath.Dir(filepath.Dir(resDir)) 99 } 100 } 101 102 if err := sanitizeDirs(resDir, dirReplacer); err != nil { 103 log.Fatal(err) 104 } 105 106 cmd := exec.Command(aapt2, []string{"compile", "--legacy", "-o", out, "--dir", resDir}...) 107 if out, err := cmd.CombinedOutput(); err != nil { 108 log.Fatalf("error compiling resources for resource directory %s: %v\n%s", resDir, err, string(out)) 109 } 110} 111 112// sanitizeDirs renames the directories that aapt is unable to parse 113func sanitizeDirs(dir string, r *strings.Replacer) error { 114 src, err := os.Open(dir) 115 if err != nil { 116 return err 117 } 118 defer src.Close() 119 120 fs, err := src.Readdir(-1) 121 if err != nil { 122 return err 123 } 124 125 for _, f := range fs { 126 if f.Mode().IsDir() { 127 if qd := r.Replace(f.Name()); qd != f.Name() { 128 if err := os.Rename(filepath.Join(dir, f.Name()), filepath.Join(dir, qd)); err != nil { 129 return err 130 } 131 } 132 } 133 } 134 return nil 135} 136