Home | History | Annotate | Download | only in genrule
      1 // Copyright 2018 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 genrule
     16 
     17 import (
     18 	"io/ioutil"
     19 	"os"
     20 	"reflect"
     21 	"strings"
     22 	"testing"
     23 
     24 	"android/soong/android"
     25 
     26 	"github.com/google/blueprint/proptools"
     27 )
     28 
     29 var buildDir string
     30 
     31 func setUp() {
     32 	var err error
     33 	buildDir, err = ioutil.TempDir("", "genrule_test")
     34 	if err != nil {
     35 		panic(err)
     36 	}
     37 }
     38 
     39 func tearDown() {
     40 	os.RemoveAll(buildDir)
     41 }
     42 
     43 func TestMain(m *testing.M) {
     44 	run := func() int {
     45 		setUp()
     46 		defer tearDown()
     47 
     48 		return m.Run()
     49 	}
     50 
     51 	os.Exit(run())
     52 }
     53 
     54 func testContext(config android.Config, bp string,
     55 	fs map[string][]byte) *android.TestContext {
     56 
     57 	ctx := android.NewTestArchContext()
     58 	ctx.RegisterModuleType("filegroup", android.ModuleFactoryAdaptor(android.FileGroupFactory))
     59 	ctx.RegisterModuleType("genrule", android.ModuleFactoryAdaptor(GenRuleFactory))
     60 	ctx.RegisterModuleType("genrule_defaults", android.ModuleFactoryAdaptor(defaultsFactory))
     61 	ctx.RegisterModuleType("tool", android.ModuleFactoryAdaptor(toolFactory))
     62 	ctx.PreArchMutators(android.RegisterDefaultsPreArchMutators)
     63 	ctx.Register()
     64 
     65 	bp += `
     66 		tool {
     67 			name: "tool",
     68 		}
     69 
     70 		filegroup {
     71 			name: "tool_files",
     72 			srcs: [
     73 				"tool_file1",
     74 				"tool_file2",
     75 			],
     76 		}
     77 
     78 		filegroup {
     79 			name: "1tool_file",
     80 			srcs: [
     81 				"tool_file1",
     82 			],
     83 		}
     84 
     85 		filegroup {
     86 			name: "ins",
     87 			srcs: [
     88 				"in1",
     89 				"in2",
     90 			],
     91 		}
     92 
     93 		filegroup {
     94 			name: "1in",
     95 			srcs: [
     96 				"in1",
     97 			],
     98 		}
     99 
    100 		filegroup {
    101 			name: "empty",
    102 		}
    103 	`
    104 
    105 	mockFS := map[string][]byte{
    106 		"Android.bp": []byte(bp),
    107 		"tool":       nil,
    108 		"tool_file1": nil,
    109 		"tool_file2": nil,
    110 		"in1":        nil,
    111 		"in2":        nil,
    112 	}
    113 
    114 	for k, v := range fs {
    115 		mockFS[k] = v
    116 	}
    117 
    118 	ctx.MockFileSystem(mockFS)
    119 
    120 	return ctx
    121 }
    122 
    123 func TestGenruleCmd(t *testing.T) {
    124 	testcases := []struct {
    125 		name string
    126 		prop string
    127 
    128 		allowMissingDependencies bool
    129 
    130 		err    string
    131 		expect string
    132 	}{
    133 		{
    134 			name: "empty location tool",
    135 			prop: `
    136 				tools: ["tool"],
    137 				out: ["out"],
    138 				cmd: "$(location) > $(out)",
    139 			`,
    140 			expect: "out/tool > __SBOX_OUT_FILES__",
    141 		},
    142 		{
    143 			name: "empty location tool2",
    144 			prop: `
    145 				tools: [":tool"],
    146 				out: ["out"],
    147 				cmd: "$(location) > $(out)",
    148 			`,
    149 			expect: "out/tool > __SBOX_OUT_FILES__",
    150 		},
    151 		{
    152 			name: "empty location tool file",
    153 			prop: `
    154 				tool_files: ["tool_file1"],
    155 				out: ["out"],
    156 				cmd: "$(location) > $(out)",
    157 			`,
    158 			expect: "tool_file1 > __SBOX_OUT_FILES__",
    159 		},
    160 		{
    161 			name: "empty location tool file fg",
    162 			prop: `
    163 				tool_files: [":1tool_file"],
    164 				out: ["out"],
    165 				cmd: "$(location) > $(out)",
    166 			`,
    167 			expect: "tool_file1 > __SBOX_OUT_FILES__",
    168 		},
    169 		{
    170 			name: "empty location tool and tool file",
    171 			prop: `
    172 				tools: ["tool"],
    173 				tool_files: ["tool_file1"],
    174 				out: ["out"],
    175 				cmd: "$(location) > $(out)",
    176 			`,
    177 			expect: "out/tool > __SBOX_OUT_FILES__",
    178 		},
    179 		{
    180 			name: "tool",
    181 			prop: `
    182 				tools: ["tool"],
    183 				out: ["out"],
    184 				cmd: "$(location tool) > $(out)",
    185 			`,
    186 			expect: "out/tool > __SBOX_OUT_FILES__",
    187 		},
    188 		{
    189 			name: "tool2",
    190 			prop: `
    191 				tools: [":tool"],
    192 				out: ["out"],
    193 				cmd: "$(location :tool) > $(out)",
    194 			`,
    195 			expect: "out/tool > __SBOX_OUT_FILES__",
    196 		},
    197 		{
    198 			name: "tool file",
    199 			prop: `
    200 				tool_files: ["tool_file1"],
    201 				out: ["out"],
    202 				cmd: "$(location tool_file1) > $(out)",
    203 			`,
    204 			expect: "tool_file1 > __SBOX_OUT_FILES__",
    205 		},
    206 		{
    207 			name: "tool file fg",
    208 			prop: `
    209 				tool_files: [":1tool_file"],
    210 				out: ["out"],
    211 				cmd: "$(location :1tool_file) > $(out)",
    212 			`,
    213 			expect: "tool_file1 > __SBOX_OUT_FILES__",
    214 		},
    215 		{
    216 			name: "tool files",
    217 			prop: `
    218 				tool_files: [":tool_files"],
    219 				out: ["out"],
    220 				cmd: "$(locations :tool_files) > $(out)",
    221 			`,
    222 			expect: "tool_file1 tool_file2 > __SBOX_OUT_FILES__",
    223 		},
    224 		{
    225 			name: "in1",
    226 			prop: `
    227 				srcs: ["in1"],
    228 				out: ["out"],
    229 				cmd: "cat $(in) > $(out)",
    230 			`,
    231 			expect: "cat ${in} > __SBOX_OUT_FILES__",
    232 		},
    233 		{
    234 			name: "in1 fg",
    235 			prop: `
    236 				srcs: [":1in"],
    237 				out: ["out"],
    238 				cmd: "cat $(in) > $(out)",
    239 			`,
    240 			expect: "cat ${in} > __SBOX_OUT_FILES__",
    241 		},
    242 		{
    243 			name: "ins",
    244 			prop: `
    245 				srcs: ["in1", "in2"],
    246 				out: ["out"],
    247 				cmd: "cat $(in) > $(out)",
    248 			`,
    249 			expect: "cat ${in} > __SBOX_OUT_FILES__",
    250 		},
    251 		{
    252 			name: "ins fg",
    253 			prop: `
    254 				srcs: [":ins"],
    255 				out: ["out"],
    256 				cmd: "cat $(in) > $(out)",
    257 			`,
    258 			expect: "cat ${in} > __SBOX_OUT_FILES__",
    259 		},
    260 		{
    261 			name: "location in1",
    262 			prop: `
    263 				srcs: ["in1"],
    264 				out: ["out"],
    265 				cmd: "cat $(location in1) > $(out)",
    266 			`,
    267 			expect: "cat in1 > __SBOX_OUT_FILES__",
    268 		},
    269 		{
    270 			name: "location in1 fg",
    271 			prop: `
    272 				srcs: [":1in"],
    273 				out: ["out"],
    274 				cmd: "cat $(location :1in) > $(out)",
    275 			`,
    276 			expect: "cat in1 > __SBOX_OUT_FILES__",
    277 		},
    278 		{
    279 			name: "location ins",
    280 			prop: `
    281 				srcs: ["in1", "in2"],
    282 				out: ["out"],
    283 				cmd: "cat $(location in1) > $(out)",
    284 			`,
    285 			expect: "cat in1 > __SBOX_OUT_FILES__",
    286 		},
    287 		{
    288 			name: "location ins fg",
    289 			prop: `
    290 				srcs: [":ins"],
    291 				out: ["out"],
    292 				cmd: "cat $(locations :ins) > $(out)",
    293 			`,
    294 			expect: "cat in1 in2 > __SBOX_OUT_FILES__",
    295 		},
    296 		{
    297 			name: "outs",
    298 			prop: `
    299 				out: ["out", "out2"],
    300 				cmd: "echo foo > $(out)",
    301 			`,
    302 			expect: "echo foo > __SBOX_OUT_FILES__",
    303 		},
    304 		{
    305 			name: "location out",
    306 			prop: `
    307 				out: ["out", "out2"],
    308 				cmd: "echo foo > $(location out2)",
    309 			`,
    310 			expect: "echo foo > __SBOX_OUT_DIR__/out2",
    311 		},
    312 		{
    313 			name: "depfile",
    314 			prop: `
    315 				out: ["out"],
    316 				depfile: true,
    317 				cmd: "echo foo > $(out) && touch $(depfile)",
    318 			`,
    319 			expect: "echo foo > __SBOX_OUT_FILES__ && touch __SBOX_DEPFILE__",
    320 		},
    321 		{
    322 			name: "gendir",
    323 			prop: `
    324 				out: ["out"],
    325 				cmd: "echo foo > $(genDir)/foo && cp $(genDir)/foo $(out)",
    326 			`,
    327 			expect: "echo foo > __SBOX_OUT_DIR__/foo && cp __SBOX_OUT_DIR__/foo __SBOX_OUT_FILES__",
    328 		},
    329 
    330 		{
    331 			name: "error empty location",
    332 			prop: `
    333 				out: ["out"],
    334 				cmd: "$(location) > $(out)",
    335 			`,
    336 			err: "at least one `tools` or `tool_files` is required if $(location) is used",
    337 		},
    338 		{
    339 			name: "error empty location no files",
    340 			prop: `
    341 				tool_files: [":empty"],
    342 				out: ["out"],
    343 				cmd: "$(location) > $(out)",
    344 			`,
    345 			err: `default label ":empty" has no files`,
    346 		},
    347 		{
    348 			name: "error empty location multiple files",
    349 			prop: `
    350 				tool_files: [":tool_files"],
    351 				out: ["out"],
    352 				cmd: "$(location) > $(out)",
    353 			`,
    354 			err: `default label ":tool_files" has multiple files`,
    355 		},
    356 		{
    357 			name: "error location",
    358 			prop: `
    359 				out: ["out"],
    360 				cmd: "echo foo > $(location missing)",
    361 			`,
    362 			err: `unknown location label "missing"`,
    363 		},
    364 		{
    365 			name: "error locations",
    366 			prop: `
    367 					out: ["out"],
    368 					cmd: "echo foo > $(locations missing)",
    369 			`,
    370 			err: `unknown locations label "missing"`,
    371 		},
    372 		{
    373 			name: "error location no files",
    374 			prop: `
    375 					out: ["out"],
    376 					srcs: [":empty"],
    377 					cmd: "echo $(location :empty) > $(out)",
    378 			`,
    379 			err: `label ":empty" has no files`,
    380 		},
    381 		{
    382 			name: "error locations no files",
    383 			prop: `
    384 					out: ["out"],
    385 					srcs: [":empty"],
    386 					cmd: "echo $(locations :empty) > $(out)",
    387 			`,
    388 			err: `label ":empty" has no files`,
    389 		},
    390 		{
    391 			name: "error location multiple files",
    392 			prop: `
    393 					out: ["out"],
    394 					srcs: [":ins"],
    395 					cmd: "echo $(location :ins) > $(out)",
    396 			`,
    397 			err: `label ":ins" has multiple files`,
    398 		},
    399 		{
    400 			name: "error variable",
    401 			prop: `
    402 					out: ["out"],
    403 					srcs: ["in1"],
    404 					cmd: "echo $(foo) > $(out)",
    405 			`,
    406 			err: `unknown variable '$(foo)'`,
    407 		},
    408 		{
    409 			name: "error depfile",
    410 			prop: `
    411 				out: ["out"],
    412 				cmd: "echo foo > $(out) && touch $(depfile)",
    413 			`,
    414 			err: "$(depfile) used without depfile property",
    415 		},
    416 		{
    417 			name: "error no depfile",
    418 			prop: `
    419 				out: ["out"],
    420 				depfile: true,
    421 				cmd: "echo foo > $(out)",
    422 			`,
    423 			err: "specified depfile=true but did not include a reference to '${depfile}' in cmd",
    424 		},
    425 		{
    426 			name: "error no out",
    427 			prop: `
    428 				cmd: "echo foo > $(out)",
    429 			`,
    430 			err: "must have at least one output file",
    431 		},
    432 		{
    433 			name: "srcs allow missing dependencies",
    434 			prop: `
    435 				srcs: [":missing"],
    436 				out: ["out"],
    437 				cmd: "cat $(location :missing) > $(out)",
    438 			`,
    439 
    440 			allowMissingDependencies: true,
    441 
    442 			expect: "cat ***missing srcs :missing*** > __SBOX_OUT_FILES__",
    443 		},
    444 		{
    445 			name: "tool allow missing dependencies",
    446 			prop: `
    447 				tools: [":missing"],
    448 				out: ["out"],
    449 				cmd: "$(location :missing) > $(out)",
    450 			`,
    451 
    452 			allowMissingDependencies: true,
    453 
    454 			expect: "***missing tool :missing*** > __SBOX_OUT_FILES__",
    455 		},
    456 	}
    457 
    458 	for _, test := range testcases {
    459 		t.Run(test.name, func(t *testing.T) {
    460 			config := android.TestArchConfig(buildDir, nil)
    461 			bp := "genrule {\n"
    462 			bp += "name: \"gen\",\n"
    463 			bp += test.prop
    464 			bp += "}\n"
    465 
    466 			config.TestProductVariables.Allow_missing_dependencies = proptools.BoolPtr(test.allowMissingDependencies)
    467 
    468 			ctx := testContext(config, bp, nil)
    469 			ctx.SetAllowMissingDependencies(test.allowMissingDependencies)
    470 
    471 			_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
    472 			if errs == nil {
    473 				_, errs = ctx.PrepareBuildActions(config)
    474 			}
    475 			if errs == nil && test.err != "" {
    476 				t.Fatalf("want error %q, got no error", test.err)
    477 			} else if errs != nil && test.err == "" {
    478 				android.FailIfErrored(t, errs)
    479 			} else if test.err != "" {
    480 				if len(errs) != 1 {
    481 					t.Errorf("want 1 error, got %d errors:", len(errs))
    482 					for _, err := range errs {
    483 						t.Errorf("   %s", err.Error())
    484 					}
    485 					t.FailNow()
    486 				}
    487 				if !strings.Contains(errs[0].Error(), test.err) {
    488 					t.Fatalf("want %q, got %q", test.err, errs[0].Error())
    489 				}
    490 				return
    491 			}
    492 
    493 			gen := ctx.ModuleForTests("gen", "").Module().(*Module)
    494 			if g, w := gen.rawCommand, "'"+test.expect+"'"; w != g {
    495 				t.Errorf("want %q, got %q", w, g)
    496 			}
    497 		})
    498 	}
    499 
    500 }
    501 
    502 func TestGenruleDefaults(t *testing.T) {
    503 	config := android.TestArchConfig(buildDir, nil)
    504 	bp := `
    505 				genrule_defaults {
    506 					name: "gen_defaults1",
    507 					cmd: "cp $(in) $(out)",
    508 				}
    509 
    510 				genrule_defaults {
    511 					name: "gen_defaults2",
    512 					srcs: ["in1"],
    513 				}
    514 
    515 				genrule {
    516 					name: "gen",
    517 					out: ["out"],
    518 					defaults: ["gen_defaults1", "gen_defaults2"],
    519 				}
    520 			`
    521 	ctx := testContext(config, bp, nil)
    522 	_, errs := ctx.ParseFileList(".", []string{"Android.bp"})
    523 	if errs == nil {
    524 		_, errs = ctx.PrepareBuildActions(config)
    525 	}
    526 	if errs != nil {
    527 		t.Fatal(errs)
    528 	}
    529 	gen := ctx.ModuleForTests("gen", "").Module().(*Module)
    530 
    531 	expectedCmd := "'cp ${in} __SBOX_OUT_FILES__'"
    532 	if gen.rawCommand != expectedCmd {
    533 		t.Errorf("Expected cmd: %q, actual: %q", expectedCmd, gen.rawCommand)
    534 	}
    535 
    536 	expectedSrcs := []string{"in1"}
    537 	if !reflect.DeepEqual(expectedSrcs, gen.properties.Srcs) {
    538 		t.Errorf("Expected srcs: %q, actual: %q", expectedSrcs, gen.properties.Srcs)
    539 	}
    540 }
    541 
    542 type testTool struct {
    543 	android.ModuleBase
    544 	outputFile android.Path
    545 }
    546 
    547 func toolFactory() android.Module {
    548 	module := &testTool{}
    549 	android.InitAndroidArchModule(module, android.HostSupported, android.MultilibFirst)
    550 	return module
    551 }
    552 
    553 func (t *testTool) GenerateAndroidBuildActions(ctx android.ModuleContext) {
    554 	t.outputFile = android.PathForTesting("out", ctx.ModuleName())
    555 }
    556 
    557 func (t *testTool) HostToolPath() android.OptionalPath {
    558 	return android.OptionalPathForPath(t.outputFile)
    559 }
    560 
    561 var _ android.HostToolProvider = (*testTool)(nil)
    562