Handle simple symlinks in mixed builds
Bug: 180945121 Test: build/bazel/ci/mixed_libc.sh Change-Id: I49fba569a41dcb8cd4c2e58560817443697f58f1
This commit is contained in:
parent
a4d9b86c8b
commit
c49e682f37
3 changed files with 253 additions and 12 deletions
|
@ -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
|
||||
|
|
|
@ -73,12 +73,13 @@ type actionGraphContainer struct {
|
|||
// BuildStatement contains information to register a build statement corresponding (one to one)
|
||||
// with a Bazel action from Bazel's action graph.
|
||||
type BuildStatement struct {
|
||||
Command string
|
||||
Depfile *string
|
||||
OutputPaths []string
|
||||
InputPaths []string
|
||||
Env []KeyValuePair
|
||||
Mnemonic string
|
||||
Command string
|
||||
Depfile *string
|
||||
OutputPaths []string
|
||||
InputPaths []string
|
||||
SymlinkPaths []string
|
||||
Env []KeyValuePair
|
||||
Mnemonic string
|
||||
}
|
||||
|
||||
// A helper type for aquery processing which facilitates retrieval of path IDs from their
|
||||
|
@ -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
|
||||
|
|
|
@ -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
|
||||
}
|
||||
|
||||
|
|
Loading…
Reference in a new issue