blob: 4174fe50a9b7d3500b0b9e993d88d034b4a79ddf [file] [log] [blame]
Andres Moralesda8706f2015-04-29 12:46:49 -07001package main
2
3import (
4 "bufio"
Andres Morales8ae47de2015-05-11 12:26:07 -07005 "errors"
Andres Moralesda8706f2015-04-29 12:46:49 -07006 "fmt"
7 "os"
8 "path"
Andres Morales8ae47de2015-05-11 12:26:07 -07009 "path/filepath"
Andres Moralesaf11df12015-04-30 12:14:34 -070010 "regexp"
Andres Moralesda8706f2015-04-29 12:46:49 -070011 "strings"
12
13 bpparser "github.com/google/blueprint/parser"
14)
15
Andres Morales8ae47de2015-05-11 12:26:07 -070016var recursiveSubdirRegex *regexp.Regexp = regexp.MustCompile("(.+)/\\*\\*/(.+)")
17
Andres Moralesda8706f2015-04-29 12:46:49 -070018type androidMkWriter struct {
19 *bufio.Writer
20
Andres Moralesaf11df12015-04-30 12:14:34 -070021 blueprint *bpparser.File
22 path string
23
Dan Willemsen360a39c2015-06-11 14:34:50 -070024 printedLocalPath bool
25
Andres Moralesaf11df12015-04-30 12:14:34 -070026 mapScope map[string][]*bpparser.Property
Andres Moralesda8706f2015-04-29 12:46:49 -070027}
28
Andres Moralesaf11df12015-04-30 12:14:34 -070029func valueToString(value bpparser.Value) string {
Andres Moralesda8706f2015-04-29 12:46:49 -070030 if value.Variable != "" {
31 return fmt.Sprintf("$(%s)", value.Variable)
Colin Crossff3b7952015-06-22 15:39:35 -070032 } else if value.Expression != nil {
33 if value.Expression.Operator != '+' {
34 panic(fmt.Errorf("unexpected operator '%c'", value.Expression.Operator))
35 }
Colin Crosseb050832015-06-22 17:26:12 -070036 return fmt.Sprintf("%s%s",
Colin Crossff3b7952015-06-22 15:39:35 -070037 valueToString(value.Expression.Args[0]),
38 valueToString(value.Expression.Args[1]))
Andres Moralesda8706f2015-04-29 12:46:49 -070039 } else {
40 switch value.Type {
41 case bpparser.Bool:
Ying Wang38284902015-06-02 18:44:59 -070042 return fmt.Sprintf("%t", value.BoolValue)
Andres Moralesda8706f2015-04-29 12:46:49 -070043 case bpparser.String:
Ying Wang38284902015-06-02 18:44:59 -070044 return fmt.Sprintf("%s", processWildcards(value.StringValue))
Andres Moralesda8706f2015-04-29 12:46:49 -070045 case bpparser.List:
Colin Crossff3b7952015-06-22 15:39:35 -070046 return fmt.Sprintf("\\\n%s", listToMkString(value.ListValue))
Andres Moralesda8706f2015-04-29 12:46:49 -070047 case bpparser.Map:
Andres Moralesaf11df12015-04-30 12:14:34 -070048 return fmt.Sprintf("ERROR can't convert map to string")
49 default:
50 return fmt.Sprintf("ERROR: unsupported type %d", value.Type)
Andres Moralesda8706f2015-04-29 12:46:49 -070051 }
52 }
Andres Moralesda8706f2015-04-29 12:46:49 -070053}
54
Andres Morales8ae47de2015-05-11 12:26:07 -070055func getTopOfAndroidTree(wd string) (string, error) {
56 if !filepath.IsAbs(wd) {
57 return "", errors.New("path must be absolute: " + wd)
58 }
59
60 topfile := "build/soong/bootstrap.bash"
61
62 for "/" != wd {
63 expected := filepath.Join(wd, topfile)
64
65 if _, err := os.Stat(expected); err == nil {
66 // Found the top
67 return wd, nil
68 }
69
70 wd = filepath.Join(wd, "..")
71 }
72
73 return "", errors.New("couldn't find top of tree from " + wd)
74}
75
Andres Moralesaf11df12015-04-30 12:14:34 -070076// TODO: handle non-recursive wildcards?
77func processWildcards(s string) string {
Andres Morales8ae47de2015-05-11 12:26:07 -070078 submatches := recursiveSubdirRegex.FindStringSubmatch(s)
79 if len(submatches) > 2 {
Andres Moralesaf11df12015-04-30 12:14:34 -070080 // Found a wildcard rule
81 return fmt.Sprintf("$(call find-files-in-subdirs, $(LOCAL_PATH), %s, %s)",
Andres Morales8ae47de2015-05-11 12:26:07 -070082 submatches[2], submatches[1])
Andres Moralesaf11df12015-04-30 12:14:34 -070083 }
84
85 return s
86}
87
88func listToMkString(list []bpparser.Value) string {
Andres Moralesda8706f2015-04-29 12:46:49 -070089 lines := make([]string, 0, len(list))
90 for _, tok := range list {
Colin Crosseb050832015-06-22 17:26:12 -070091 lines = append(lines, fmt.Sprintf(" %s", valueToString(tok)))
Andres Moralesda8706f2015-04-29 12:46:49 -070092 }
93
94 return strings.Join(lines, " \\\n")
95}
96
Andres Moralesaf11df12015-04-30 12:14:34 -070097func translateTargetConditionals(props []*bpparser.Property,
98 disabledBuilds map[string]bool, isHostRule bool) (computedProps []string) {
99 for _, target := range props {
100 conditionals := targetScopedPropertyConditionals
Dan Willemsen68fdfcc2015-06-11 14:05:01 -0700101 altConditionals := hostScopedPropertyConditionals
Andres Moralesaf11df12015-04-30 12:14:34 -0700102 if isHostRule {
Dan Willemsen68fdfcc2015-06-11 14:05:01 -0700103 conditionals, altConditionals = altConditionals, conditionals
Andres Moralesaf11df12015-04-30 12:14:34 -0700104 }
105
106 conditional, ok := conditionals[target.Name.Name]
107 if !ok {
Dan Willemsen68fdfcc2015-06-11 14:05:01 -0700108 if _, ok := altConditionals[target.Name.Name]; ok {
109 // This is only for the other build type
110 continue
111 } else {
112 // not found
113 conditional = fmt.Sprintf(
114 "ifeq(true, true) # ERROR: unsupported conditional [%s]",
115 target.Name.Name)
116 }
Andres Moralesaf11df12015-04-30 12:14:34 -0700117 }
118
119 var scopedProps []string
120 for _, targetScopedProp := range target.Value.MapValue {
121 if mkProp, ok := standardProperties[targetScopedProp.Name.Name]; ok {
122 scopedProps = append(scopedProps, fmt.Sprintf("%s += %s",
123 mkProp.string, valueToString(targetScopedProp.Value)))
Dan Willemsen57ad08c2015-06-10 16:20:14 -0700124 } else if rwProp, ok := rewriteProperties[targetScopedProp.Name.Name]; ok {
125 scopedProps = append(scopedProps, rwProp.f(rwProp.string, targetScopedProp, nil)...)
Andres Moralesaf11df12015-04-30 12:14:34 -0700126 } else if "disabled" == targetScopedProp.Name.Name {
127 if targetScopedProp.Value.BoolValue {
128 disabledBuilds[target.Name.Name] = true
129 } else {
130 delete(disabledBuilds, target.Name.Name)
131 }
132 }
133 }
134
135 if len(scopedProps) > 0 {
Dan Willemsen68fdfcc2015-06-11 14:05:01 -0700136 if conditional != "" {
137 computedProps = append(computedProps, conditional)
138 computedProps = append(computedProps, scopedProps...)
139 computedProps = append(computedProps, "endif")
140 } else {
141 computedProps = append(computedProps, scopedProps...)
142 }
Andres Moralesaf11df12015-04-30 12:14:34 -0700143 }
Andres Moralesda8706f2015-04-29 12:46:49 -0700144 }
Andres Moralesaf11df12015-04-30 12:14:34 -0700145
146 return
147}
148
149func translateSuffixProperties(suffixProps []*bpparser.Property,
150 suffixMap map[string]string) (computedProps []string) {
151 for _, suffixProp := range suffixProps {
152 if suffix, ok := suffixMap[suffixProp.Name.Name]; ok {
153 for _, stdProp := range suffixProp.Value.MapValue {
154 if mkProp, ok := standardProperties[stdProp.Name.Name]; ok {
155 computedProps = append(computedProps, fmt.Sprintf("%s_%s := %s", mkProp.string, suffix, valueToString(stdProp.Value)))
Dan Willemsen57ad08c2015-06-10 16:20:14 -0700156 } else if rwProp, ok := rewriteProperties[stdProp.Name.Name]; ok {
157 computedProps = append(computedProps, rwProp.f(rwProp.string, stdProp, &suffix)...)
Andres Moralesaf11df12015-04-30 12:14:34 -0700158 } else {
159 computedProps = append(computedProps, fmt.Sprintf("# ERROR: unsupported property %s", stdProp.Name.Name))
160 }
161 }
162 }
163 }
164 return
165}
166
Dan Willemsen57ad08c2015-06-10 16:20:14 -0700167func prependLocalPath(name string, prop *bpparser.Property, suffix *string) (computedProps []string) {
Dan Willemsen57ad08c2015-06-10 16:20:14 -0700168 if suffix != nil {
169 name += "_" + *suffix
170 }
Colin Crossff3b7952015-06-22 15:39:35 -0700171 return []string{
172 fmt.Sprintf("%s := $(addprefix $(LOCAL_PATH)/,%s)\n", name, valueToString(prop.Value)),
173 }
Dan Willemsen57ad08c2015-06-10 16:20:14 -0700174}
175
Dan Willemsen1d9f2792015-06-22 15:40:14 -0700176func prependLocalModule(name string, prop *bpparser.Property, suffix *string) (computedProps []string) {
177 if suffix != nil {
178 name += "_" + *suffix
179 }
180 return []string {
Dan Willemsend7b11dd2015-06-22 16:25:39 -0700181 fmt.Sprintf("%s := $(LOCAL_MODULE)%s\n", name, valueToString(prop.Value)),
Dan Willemsen1d9f2792015-06-22 15:40:14 -0700182 }
183}
184
Andres Moralesaf11df12015-04-30 12:14:34 -0700185func (w *androidMkWriter) lookupMap(parent bpparser.Value) (mapValue []*bpparser.Property) {
186 if parent.Variable != "" {
187 mapValue = w.mapScope[parent.Variable]
188 } else {
189 mapValue = parent.MapValue
190 }
191 return
Andres Moralesda8706f2015-04-29 12:46:49 -0700192}
193
194func (w *androidMkWriter) handleComment(comment *bpparser.Comment) {
195 for _, c := range comment.Comment {
Andres Moralesaf11df12015-04-30 12:14:34 -0700196 fmt.Fprintf(w, "#%s\n", c)
Andres Moralesda8706f2015-04-29 12:46:49 -0700197 }
198}
199
Andres Moralesaf11df12015-04-30 12:14:34 -0700200func (w *androidMkWriter) writeModule(moduleRule string, props []string,
201 disabledBuilds map[string]bool, isHostRule bool) {
202 disabledConditionals := disabledTargetConditionals
203 if isHostRule {
204 disabledConditionals = disabledHostConditionals
205 }
206 for build, _ := range disabledBuilds {
207 if conditional, ok := disabledConditionals[build]; ok {
208 fmt.Fprintf(w, "%s\n", conditional)
209 defer fmt.Fprintf(w, "endif\n")
Andres Moralesda8706f2015-04-29 12:46:49 -0700210 }
Andres Moralesaf11df12015-04-30 12:14:34 -0700211 }
Andres Moralesda8706f2015-04-29 12:46:49 -0700212
Andres Moralesaf11df12015-04-30 12:14:34 -0700213 fmt.Fprintf(w, "include $(CLEAR_VARS)\n")
214 fmt.Fprintf(w, "%s\n", strings.Join(props, "\n"))
215 fmt.Fprintf(w, "include $(%s)\n\n", moduleRule)
216}
Andres Moralesda8706f2015-04-29 12:46:49 -0700217
Dan Willemsen68fdfcc2015-06-11 14:05:01 -0700218func (w *androidMkWriter) parsePropsAndWriteModule(moduleRule string, isHostRule bool, module *bpparser.Module) (hostSupported bool) {
Andres Moralesaf11df12015-04-30 12:14:34 -0700219 standardProps := make([]string, 0, len(module.Properties))
220 disabledBuilds := make(map[string]bool)
221 for _, prop := range module.Properties {
222 if mkProp, ok := standardProperties[prop.Name.Name]; ok {
223 standardProps = append(standardProps, fmt.Sprintf("%s := %s", mkProp.string, valueToString(prop.Value)))
Dan Willemsen57ad08c2015-06-10 16:20:14 -0700224 } else if rwProp, ok := rewriteProperties[prop.Name.Name]; ok {
225 standardProps = append(standardProps, rwProp.f(rwProp.string, prop, nil)...)
Andres Moralesaf11df12015-04-30 12:14:34 -0700226 } else if suffixMap, ok := suffixProperties[prop.Name.Name]; ok {
227 suffixProps := w.lookupMap(prop.Value)
228 standardProps = append(standardProps, translateSuffixProperties(suffixProps, suffixMap)...)
229 } else if "target" == prop.Name.Name {
230 props := w.lookupMap(prop.Value)
231 standardProps = append(standardProps, translateTargetConditionals(props, disabledBuilds, isHostRule)...)
232 } else if "host_supported" == prop.Name.Name {
233 hostSupported = prop.Value.BoolValue
234 } else {
235 standardProps = append(standardProps, fmt.Sprintf("# ERROR: Unsupported property %s", prop.Name.Name))
236 }
237 }
238
239 // write out target build
240 w.writeModule(moduleRule, standardProps, disabledBuilds, isHostRule)
Dan Willemsen68fdfcc2015-06-11 14:05:01 -0700241 return
242}
243
Dan Willemsen49f50452015-06-24 14:56:00 -0700244func (w *androidMkWriter) mutateModule(module *bpparser.Module) (modules []*bpparser.Module) {
245 if module.Type.Name == "cc_library" {
246 modules = append(modules, &bpparser.Module{
247 Type: bpparser.Ident{
248 Name: "cc_library_shared",
249 Pos: module.Type.Pos,
250 },
251 Properties: module.Properties,
252 LbracePos: module.LbracePos,
253 RbracePos: module.RbracePos,
254 })
255
256 modules = append(modules, &bpparser.Module{
257 Type: bpparser.Ident{
258 Name: "cc_library_static",
259 Pos: module.Type.Pos,
260 },
261 Properties: module.Properties,
262 LbracePos: module.LbracePos,
263 RbracePos: module.RbracePos,
264 })
265 } else {
266 modules = []*bpparser.Module{module}
Dan Willemsen68fdfcc2015-06-11 14:05:01 -0700267 }
268
Dan Willemsen49f50452015-06-24 14:56:00 -0700269 return
270}
Dan Willemsen68fdfcc2015-06-11 14:05:01 -0700271
Dan Willemsen49f50452015-06-24 14:56:00 -0700272func (w *androidMkWriter) handleModule(inputModule *bpparser.Module) {
273 modules := w.mutateModule(inputModule)
274
275 for _, module := range modules {
276 moduleRule := fmt.Sprintf(module.Type.Name)
277 if translation, ok := moduleTypeToRule[module.Type.Name]; ok {
278 moduleRule = translation
Andres Moralesaf11df12015-04-30 12:14:34 -0700279 }
Dan Willemsen68fdfcc2015-06-11 14:05:01 -0700280
Dan Willemsen49f50452015-06-24 14:56:00 -0700281 isHostRule := strings.Contains(moduleRule, "HOST")
282 hostSupported := w.parsePropsAndWriteModule(moduleRule, isHostRule, module)
283
284 if !isHostRule && hostSupported {
285 hostModuleRule := "NO CORRESPONDING HOST RULE" + moduleRule
286 if trans, ok := targetToHostModuleRule[moduleRule]; ok {
287 hostModuleRule = trans
288 }
289
290 w.parsePropsAndWriteModule(hostModuleRule, true, module)
291 }
Andres Moralesaf11df12015-04-30 12:14:34 -0700292 }
293}
294
295func (w *androidMkWriter) handleSubdirs(value bpparser.Value) {
Andres Morales8ae47de2015-05-11 12:26:07 -0700296 subdirs := make([]string, 0, len(value.ListValue))
297 for _, tok := range value.ListValue {
298 subdirs = append(subdirs, tok.StringValue)
Andres Moralesda8706f2015-04-29 12:46:49 -0700299 }
Ying Wang38284902015-06-02 18:44:59 -0700300 // The current makefile may be generated to outside the source tree (such as the out directory), with a different structure.
301 fmt.Fprintf(w, "# Uncomment the following line if you really want to include subdir Android.mks.\n")
302 fmt.Fprintf(w, "# include $(wildcard $(addsuffix $(LOCAL_PATH)/%s/, Android.mk))\n", strings.Join(subdirs, " "))
Andres Moralesda8706f2015-04-29 12:46:49 -0700303}
304
305func (w *androidMkWriter) handleAssignment(assignment *bpparser.Assignment) {
Andres Moralesaf11df12015-04-30 12:14:34 -0700306 if "subdirs" == assignment.Name.Name {
307 w.handleSubdirs(assignment.OrigValue)
308 } else if assignment.OrigValue.Type == bpparser.Map {
309 // maps may be assigned in Soong, but can only be translated to .mk
310 // in the context of the module
311 w.mapScope[assignment.Name.Name] = assignment.OrigValue.MapValue
312 } else {
313 assigner := ":="
314 if assignment.Assigner != "=" {
315 assigner = assignment.Assigner
316 }
317 fmt.Fprintf(w, "%s %s %s\n", assignment.Name.Name, assigner,
318 valueToString(assignment.OrigValue))
Andres Moralesda8706f2015-04-29 12:46:49 -0700319 }
Andres Moralesda8706f2015-04-29 12:46:49 -0700320}
321
322func (w *androidMkWriter) iter() <-chan interface{} {
Andres Moralesaf11df12015-04-30 12:14:34 -0700323 ch := make(chan interface{}, len(w.blueprint.Comments)+len(w.blueprint.Defs))
Andres Moralesda8706f2015-04-29 12:46:49 -0700324 go func() {
325 commIdx := 0
326 defsIdx := 0
Andres Moralesaf11df12015-04-30 12:14:34 -0700327 for defsIdx < len(w.blueprint.Defs) || commIdx < len(w.blueprint.Comments) {
328 if defsIdx == len(w.blueprint.Defs) {
329 ch <- w.blueprint.Comments[commIdx]
Andres Moralesda8706f2015-04-29 12:46:49 -0700330 commIdx++
Andres Moralesaf11df12015-04-30 12:14:34 -0700331 } else if commIdx == len(w.blueprint.Comments) {
332 ch <- w.blueprint.Defs[defsIdx]
Andres Moralesda8706f2015-04-29 12:46:49 -0700333 defsIdx++
334 } else {
335 commentsPos := 0
336 defsPos := 0
337
Andres Moralesaf11df12015-04-30 12:14:34 -0700338 def := w.blueprint.Defs[defsIdx]
Andres Moralesda8706f2015-04-29 12:46:49 -0700339 switch def := def.(type) {
340 case *bpparser.Module:
341 defsPos = def.LbracePos.Line
342 case *bpparser.Assignment:
343 defsPos = def.Pos.Line
344 }
345
Andres Moralesaf11df12015-04-30 12:14:34 -0700346 comment := w.blueprint.Comments[commIdx]
Andres Moralesda8706f2015-04-29 12:46:49 -0700347 commentsPos = comment.Pos.Line
348
349 if commentsPos < defsPos {
350 commIdx++
351 ch <- comment
352 } else {
353 defsIdx++
354 ch <- def
355 }
356 }
357 }
358 close(ch)
359 }()
360 return ch
361}
362
Andres Morales8ae47de2015-05-11 12:26:07 -0700363func (w *androidMkWriter) handleLocalPath() error {
Dan Willemsen360a39c2015-06-11 14:34:50 -0700364 if w.printedLocalPath {
365 return nil
366 }
367 w.printedLocalPath = true
368
Ying Wang38284902015-06-02 18:44:59 -0700369 localPath, err := filepath.Abs(w.path)
Andres Morales8ae47de2015-05-11 12:26:07 -0700370 if err != nil {
371 return err
372 }
373
Ying Wang38284902015-06-02 18:44:59 -0700374 top, err := getTopOfAndroidTree(localPath)
Andres Morales8ae47de2015-05-11 12:26:07 -0700375 if err != nil {
376 return err
377 }
378
Ying Wang38284902015-06-02 18:44:59 -0700379 rel, err := filepath.Rel(top, localPath)
Andres Morales8ae47de2015-05-11 12:26:07 -0700380 if err != nil {
381 return err
382 }
383
384 w.WriteString("LOCAL_PATH := " + rel + "\n")
Dan Willemsenc2666e62015-06-10 16:18:58 -0700385 w.WriteString("LOCAL_MODULE_MAKEFILE := $(lastword $(MAKEFILE_LIST))\n\n")
Andres Morales8ae47de2015-05-11 12:26:07 -0700386 return nil
387}
388
Ying Wang38284902015-06-02 18:44:59 -0700389func (w *androidMkWriter) write(androidMk string) error {
390 fmt.Printf("Writing %s\n", androidMk)
Andres Moralesda8706f2015-04-29 12:46:49 -0700391
Ying Wang38284902015-06-02 18:44:59 -0700392 f, err := os.Create(androidMk)
Andres Moralesda8706f2015-04-29 12:46:49 -0700393 if err != nil {
394 panic(err)
395 }
396
Andres Moralesaf11df12015-04-30 12:14:34 -0700397 defer f.Close()
Andres Moralesda8706f2015-04-29 12:46:49 -0700398
399 w.Writer = bufio.NewWriter(f)
400
401 for block := range w.iter() {
402 switch block := block.(type) {
403 case *bpparser.Module:
Dan Willemsen360a39c2015-06-11 14:34:50 -0700404 if err := w.handleLocalPath(); err != nil {
405 return err
406 }
Andres Moralesda8706f2015-04-29 12:46:49 -0700407 w.handleModule(block)
408 case *bpparser.Assignment:
Dan Willemsen360a39c2015-06-11 14:34:50 -0700409 if err := w.handleLocalPath(); err != nil {
410 return err
411 }
Andres Moralesda8706f2015-04-29 12:46:49 -0700412 w.handleAssignment(block)
413 case bpparser.Comment:
414 w.handleComment(&block)
415 }
416 }
417
418 if err = w.Flush(); err != nil {
419 panic(err)
420 }
Ying Wang38284902015-06-02 18:44:59 -0700421 return nil
Andres Moralesda8706f2015-04-29 12:46:49 -0700422}
423
424func main() {
425 if len(os.Args) < 2 {
426 fmt.Println("No filename supplied")
Ying Wang38284902015-06-02 18:44:59 -0700427 os.Exit(1)
Andres Moralesda8706f2015-04-29 12:46:49 -0700428 }
429
Ying Wang38284902015-06-02 18:44:59 -0700430 androidBp := os.Args[1]
431 var androidMk string
432 if len(os.Args) >= 3 {
433 androidMk = os.Args[2]
434 } else {
435 androidMk = androidBp + ".mk"
436 }
437
438 reader, err := os.Open(androidBp)
Andres Moralesda8706f2015-04-29 12:46:49 -0700439 if err != nil {
440 fmt.Println(err.Error())
Ying Wang38284902015-06-02 18:44:59 -0700441 os.Exit(1)
Andres Moralesda8706f2015-04-29 12:46:49 -0700442 }
443
444 scope := bpparser.NewScope(nil)
Ying Wang38284902015-06-02 18:44:59 -0700445 blueprint, errs := bpparser.Parse(androidBp, reader, scope)
Andres Moralesda8706f2015-04-29 12:46:49 -0700446 if len(errs) > 0 {
Ying Wang38284902015-06-02 18:44:59 -0700447 fmt.Println("%d errors parsing %s", len(errs), androidBp)
Andres Moralesda8706f2015-04-29 12:46:49 -0700448 fmt.Println(errs)
Ying Wang38284902015-06-02 18:44:59 -0700449 os.Exit(1)
Andres Moralesda8706f2015-04-29 12:46:49 -0700450 }
451
452 writer := &androidMkWriter{
Andres Moralesaf11df12015-04-30 12:14:34 -0700453 blueprint: blueprint,
Ying Wang38284902015-06-02 18:44:59 -0700454 path: path.Dir(androidBp),
Andres Moralesaf11df12015-04-30 12:14:34 -0700455 mapScope: make(map[string][]*bpparser.Property),
Andres Moralesda8706f2015-04-29 12:46:49 -0700456 }
457
Ying Wang38284902015-06-02 18:44:59 -0700458 err = writer.write(androidMk)
459 if err != nil {
460 fmt.Println(err.Error())
461 os.Exit(1)
462 }
Andres Moralesda8706f2015-04-29 12:46:49 -0700463}