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

Commit 6ae56583 authored by Liz Kammer's avatar Liz Kammer Committed by Gerrit Code Review
Browse files

Merge "Handle simple symlinks in mixed builds"

parents 84c1cdf3 c49e682f
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
}