Donate to e Foundation | Murena handsets with /e/OS | Own a part of Murena! Learn more

Commit c49e682f authored by Liz Kammer's avatar Liz Kammer
Browse files

Handle simple symlinks in mixed builds

Bug: 180945121
Test: build/bazel/ci/mixed_libc.sh
Change-Id: I49fba569a41dcb8cd4c2e58560817443697f58f1
parent a4d9b86c
Loading
Loading
Loading
Loading
+4 −0
Original line number Diff line number Diff line
@@ -756,6 +756,10 @@ func (c *bazelSingleton) GenerateBuildActions(ctx SingletonContext) {
			cmd.ImplicitDepFile(PathForBazelOut(ctx, *depfile))
		}

		for _, symlinkPath := range buildStatement.SymlinkPaths {
			cmd.ImplicitSymlinkOutput(PathForBazelOut(ctx, symlinkPath))
		}

		// This is required to silence warnings pertaining to unexpected timestamps. Particularly,
		// some Bazel builtins (such as files in the bazel_tools directory) have far-future
		// timestamps. Without restat, Ninja would emit warnings that the input files of a
+30 −11
Original line number Diff line number Diff line
@@ -77,6 +77,7 @@ type BuildStatement struct {
	Depfile      *string
	OutputPaths  []string
	InputPaths   []string
	SymlinkPaths []string
	Env          []KeyValuePair
	Mnemonic     string
}
@@ -234,10 +235,21 @@ func AqueryBuildStatements(aqueryJsonProto []byte) ([]BuildStatement, error) {
			OutputPaths: outputPaths,
			InputPaths:  inputPaths,
			Env:         actionEntry.EnvironmentVariables,
			Mnemonic:    actionEntry.Mnemonic}
		if len(actionEntry.Arguments) < 1 {
			Mnemonic:    actionEntry.Mnemonic,
		}

		if isSymlinkAction(actionEntry) {
			if len(inputPaths) != 1 || len(outputPaths) != 1 {
				return nil, fmt.Errorf("Expect 1 input and 1 output to symlink action, got: input %q, output %q", inputPaths, outputPaths)
			}
			out := outputPaths[0]
			outDir := proptools.ShellEscapeIncludingSpaces(filepath.Dir(out))
			out = proptools.ShellEscapeIncludingSpaces(out)
			in := proptools.ShellEscapeIncludingSpaces(inputPaths[0])
			buildStatement.Command = fmt.Sprintf("mkdir -p %[1]s && rm -f %[2]s && ln -rsf %[3]s %[2]s", outDir, out, in)
			buildStatement.SymlinkPaths = outputPaths[:]
		} else if len(actionEntry.Arguments) < 1 {
			return nil, fmt.Errorf("received action with no command: [%v]", buildStatement)
			continue
		}
		buildStatements = append(buildStatements, buildStatement)
	}
@@ -245,9 +257,13 @@ func AqueryBuildStatements(aqueryJsonProto []byte) ([]BuildStatement, error) {
	return buildStatements, nil
}

func isSymlinkAction(a action) bool {
	return a.Mnemonic == "Symlink" || a.Mnemonic == "SolibSymlink"
}

func shouldSkipAction(a action) bool {
	// TODO(b/180945121): Handle symlink actions.
	if a.Mnemonic == "Symlink" || a.Mnemonic == "SourceSymlinkManifest" || a.Mnemonic == "SymlinkTree" || a.Mnemonic == "SolibSymlink" {
	// TODO(b/180945121): Handle complex symlink actions.
	if a.Mnemonic == "SymlinkTree" || a.Mnemonic == "SourceSymlinkManifest" {
		return true
	}
	// Middleman actions are not handled like other actions; they are handled separately as a
@@ -278,6 +294,9 @@ func expandPathFragment(id int, pathFragmentsMap map[int]pathFragment) (string,
			return "", fmt.Errorf("undefined path fragment id %d", currId)
		}
		labels = append([]string{currFragment.Label}, labels...)
		if currId == currFragment.ParentId {
			return "", fmt.Errorf("Fragment cannot refer to itself as parent %#v", currFragment)
		}
		currId = currFragment.ParentId
	}
	return filepath.Join(labels...), nil
+219 −1
Original line number Diff line number Diff line
@@ -805,17 +805,229 @@ func TestMiddlemenAction(t *testing.T) {
	}
}

func TestSimpleSymlink(t *testing.T) {
	const inputString = `
{
  "artifacts": [{
    "id": 1,
    "pathFragmentId": 3
  }, {
    "id": 2,
    "pathFragmentId": 5
  }],
  "actions": [{
    "targetId": 1,
    "actionKey": "x",
    "mnemonic": "Symlink",
    "inputDepSetIds": [1],
    "outputIds": [2],
    "primaryOutputId": 2
  }],
  "depSetOfFiles": [{
    "id": 1,
    "directArtifactIds": [1]
  }],
  "pathFragments": [{
    "id": 1,
    "label": "one"
  }, {
    "id": 2,
    "label": "file_subdir",
    "parentId": 1
  }, {
    "id": 3,
    "label": "file",
    "parentId": 2
  }, {
    "id": 4,
    "label": "symlink_subdir",
    "parentId": 1
  }, {
    "id": 5,
    "label": "symlink",
    "parentId": 4
  }]
}`

	actual, err := AqueryBuildStatements([]byte(inputString))

	if err != nil {
		t.Errorf("Unexpected error %q", err)
	}

	expectedBuildStatements := []BuildStatement{
		BuildStatement{
			Command: "mkdir -p one/symlink_subdir && " +
				"rm -f one/symlink_subdir/symlink && " +
				"ln -rsf one/file_subdir/file one/symlink_subdir/symlink",
			InputPaths:   []string{"one/file_subdir/file"},
			OutputPaths:  []string{"one/symlink_subdir/symlink"},
			SymlinkPaths: []string{"one/symlink_subdir/symlink"},
			Mnemonic:     "Symlink",
		},
	}
	assertBuildStatements(t, actual, expectedBuildStatements)
}

func TestSymlinkQuotesPaths(t *testing.T) {
	const inputString = `
{
  "artifacts": [{
    "id": 1,
    "pathFragmentId": 3
  }, {
    "id": 2,
    "pathFragmentId": 5
  }],
  "actions": [{
    "targetId": 1,
    "actionKey": "x",
    "mnemonic": "SolibSymlink",
    "inputDepSetIds": [1],
    "outputIds": [2],
    "primaryOutputId": 2
  }],
  "depSetOfFiles": [{
    "id": 1,
    "directArtifactIds": [1]
  }],
  "pathFragments": [{
    "id": 1,
    "label": "one"
  }, {
    "id": 2,
    "label": "file subdir",
    "parentId": 1
  }, {
    "id": 3,
    "label": "file",
    "parentId": 2
  }, {
    "id": 4,
    "label": "symlink subdir",
    "parentId": 1
  }, {
    "id": 5,
    "label": "symlink",
    "parentId": 4
  }]
}`

	actual, err := AqueryBuildStatements([]byte(inputString))

	if err != nil {
		t.Errorf("Unexpected error %q", err)
	}

	expectedBuildStatements := []BuildStatement{
		BuildStatement{
			Command: "mkdir -p 'one/symlink subdir' && " +
				"rm -f 'one/symlink subdir/symlink' && " +
				"ln -rsf 'one/file subdir/file' 'one/symlink subdir/symlink'",
			InputPaths:   []string{"one/file subdir/file"},
			OutputPaths:  []string{"one/symlink subdir/symlink"},
			SymlinkPaths: []string{"one/symlink subdir/symlink"},
			Mnemonic:     "SolibSymlink",
		},
	}
	assertBuildStatements(t, actual, expectedBuildStatements)
}

func TestSymlinkMultipleInputs(t *testing.T) {
	const inputString = `
{
  "artifacts": [{
    "id": 1,
    "pathFragmentId": 1
  }, {
    "id": 2,
    "pathFragmentId": 2
  }, {
    "id": 3,
    "pathFragmentId": 3
  }],
  "actions": [{
    "targetId": 1,
    "actionKey": "x",
    "mnemonic": "Symlink",
    "inputDepSetIds": [1],
    "outputIds": [3],
    "primaryOutputId": 3
  }],
  "depSetOfFiles": [{
    "id": 1,
    "directArtifactIds": [1,2]
  }],
  "pathFragments": [{
    "id": 1,
    "label": "file"
  }, {
    "id": 2,
    "label": "other_file"
  }, {
    "id": 3,
    "label": "symlink"
  }]
}`

	_, err := AqueryBuildStatements([]byte(inputString))
	assertError(t, err, `Expect 1 input and 1 output to symlink action, got: input ["file" "other_file"], output ["symlink"]`)
}

func TestSymlinkMultipleOutputs(t *testing.T) {
	const inputString = `
{
  "artifacts": [{
    "id": 1,
    "pathFragmentId": 1
  }, {
    "id": 2,
    "pathFragmentId": 2
  }, {
    "id": 3,
    "pathFragmentId": 3
  }],
  "actions": [{
    "targetId": 1,
    "actionKey": "x",
    "mnemonic": "Symlink",
    "inputDepSetIds": [1],
    "outputIds": [2,3],
    "primaryOutputId": 2
  }],
  "depSetOfFiles": [{
    "id": 1,
    "directArtifactIds": [1]
  }],
  "pathFragments": [{
    "id": 1,
    "label": "file"
  }, {
    "id": 2,
    "label": "symlink"
  }, {
    "id": 3,
    "label": "other_symlink"
  }]
}`

	_, err := AqueryBuildStatements([]byte(inputString))
	assertError(t, err, `Expect 1 input and 1 output to symlink action, got: input ["file"], output ["symlink" "other_symlink"]`)
}

func assertError(t *testing.T, err error, expected string) {
	t.Helper()
	if err == nil {
		t.Errorf("expected error '%s', but got no error", expected)
	} else if err.Error() != expected {
		t.Errorf("expected error '%s', but got: %s", expected, err.Error())
		t.Errorf("expected error:\n\t'%s', but got:\n\t'%s'", expected, err.Error())
	}
}

// Asserts that the given actual build statements match the given expected build statements.
// Build statement equivalence is determined using buildStatementEquals.
func assertBuildStatements(t *testing.T, expected []BuildStatement, actual []BuildStatement) {
	t.Helper()
	if len(expected) != len(actual) {
		t.Errorf("expected %d build statements, but got %d,\n expected: %v,\n actual: %v",
			len(expected), len(actual), expected, actual)
@@ -852,6 +1064,12 @@ func buildStatementEquals(first BuildStatement, second BuildStatement) bool {
	if !reflect.DeepEqual(stringSet(first.OutputPaths), stringSet(second.OutputPaths)) {
		return false
	}
	if !reflect.DeepEqual(stringSet(first.SymlinkPaths), stringSet(second.SymlinkPaths)) {
		return false
	}
	if first.Depfile != second.Depfile {
		return false
	}
	return true
}