Merge "Handle simple symlinks in mixed builds"
This commit is contained in:
@@ -756,6 +756,10 @@ func (c *bazelSingleton) GenerateBuildActions(ctx SingletonContext) {
|
|||||||
cmd.ImplicitDepFile(PathForBazelOut(ctx, *depfile))
|
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,
|
// 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
|
// 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
|
// 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)
|
// BuildStatement contains information to register a build statement corresponding (one to one)
|
||||||
// with a Bazel action from Bazel's action graph.
|
// with a Bazel action from Bazel's action graph.
|
||||||
type BuildStatement struct {
|
type BuildStatement struct {
|
||||||
Command string
|
Command string
|
||||||
Depfile *string
|
Depfile *string
|
||||||
OutputPaths []string
|
OutputPaths []string
|
||||||
InputPaths []string
|
InputPaths []string
|
||||||
Env []KeyValuePair
|
SymlinkPaths []string
|
||||||
Mnemonic string
|
Env []KeyValuePair
|
||||||
|
Mnemonic string
|
||||||
}
|
}
|
||||||
|
|
||||||
// A helper type for aquery processing which facilitates retrieval of path IDs from their
|
// 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,
|
OutputPaths: outputPaths,
|
||||||
InputPaths: inputPaths,
|
InputPaths: inputPaths,
|
||||||
Env: actionEntry.EnvironmentVariables,
|
Env: actionEntry.EnvironmentVariables,
|
||||||
Mnemonic: actionEntry.Mnemonic}
|
Mnemonic: actionEntry.Mnemonic,
|
||||||
if len(actionEntry.Arguments) < 1 {
|
}
|
||||||
|
|
||||||
|
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)
|
return nil, fmt.Errorf("received action with no command: [%v]", buildStatement)
|
||||||
continue
|
|
||||||
}
|
}
|
||||||
buildStatements = append(buildStatements, buildStatement)
|
buildStatements = append(buildStatements, buildStatement)
|
||||||
}
|
}
|
||||||
@@ -245,9 +257,13 @@ func AqueryBuildStatements(aqueryJsonProto []byte) ([]BuildStatement, error) {
|
|||||||
return buildStatements, nil
|
return buildStatements, nil
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func isSymlinkAction(a action) bool {
|
||||||
|
return a.Mnemonic == "Symlink" || a.Mnemonic == "SolibSymlink"
|
||||||
|
}
|
||||||
|
|
||||||
func shouldSkipAction(a action) bool {
|
func shouldSkipAction(a action) bool {
|
||||||
// TODO(b/180945121): Handle symlink actions.
|
// TODO(b/180945121): Handle complex symlink actions.
|
||||||
if a.Mnemonic == "Symlink" || a.Mnemonic == "SourceSymlinkManifest" || a.Mnemonic == "SymlinkTree" || a.Mnemonic == "SolibSymlink" {
|
if a.Mnemonic == "SymlinkTree" || a.Mnemonic == "SourceSymlinkManifest" {
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
// Middleman actions are not handled like other actions; they are handled separately as a
|
// 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)
|
return "", fmt.Errorf("undefined path fragment id %d", currId)
|
||||||
}
|
}
|
||||||
labels = append([]string{currFragment.Label}, labels...)
|
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
|
currId = currFragment.ParentId
|
||||||
}
|
}
|
||||||
return filepath.Join(labels...), nil
|
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) {
|
func assertError(t *testing.T, err error, expected string) {
|
||||||
|
t.Helper()
|
||||||
if err == nil {
|
if err == nil {
|
||||||
t.Errorf("expected error '%s', but got no error", expected)
|
t.Errorf("expected error '%s', but got no error", expected)
|
||||||
} else if err.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.
|
// Asserts that the given actual build statements match the given expected build statements.
|
||||||
// Build statement equivalence is determined using buildStatementEquals.
|
// Build statement equivalence is determined using buildStatementEquals.
|
||||||
func assertBuildStatements(t *testing.T, expected []BuildStatement, actual []BuildStatement) {
|
func assertBuildStatements(t *testing.T, expected []BuildStatement, actual []BuildStatement) {
|
||||||
|
t.Helper()
|
||||||
if len(expected) != len(actual) {
|
if len(expected) != len(actual) {
|
||||||
t.Errorf("expected %d build statements, but got %d,\n expected: %v,\n actual: %v",
|
t.Errorf("expected %d build statements, but got %d,\n expected: %v,\n actual: %v",
|
||||||
len(expected), len(actual), expected, actual)
|
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)) {
|
if !reflect.DeepEqual(stringSet(first.OutputPaths), stringSet(second.OutputPaths)) {
|
||||||
return false
|
return false
|
||||||
}
|
}
|
||||||
|
if !reflect.DeepEqual(stringSet(first.SymlinkPaths), stringSet(second.SymlinkPaths)) {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
if first.Depfile != second.Depfile {
|
||||||
|
return false
|
||||||
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
Reference in New Issue
Block a user