Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

[internal] Hook up check to Go #13322

Merged
merged 1 commit into from
Oct 22, 2021
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 2 additions & 1 deletion src/python/pants/backend/experimental/go/register.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from pants.backend.go import target_type_rules
from pants.backend.go.goals import package_binary, run_binary, tailor, test
from pants.backend.go.goals import check, package_binary, run_binary, tailor, test
from pants.backend.go.lint import fmt
from pants.backend.go.lint.gofmt import skip_field as gofmt_skip_field
from pants.backend.go.lint.gofmt.rules import rules as gofmt_rules
Expand Down Expand Up @@ -34,6 +34,7 @@ def rules():
return [
*assembly.rules(),
*build_pkg.rules(),
*check.rules(),
*third_party_pkg.rules(),
*golang.rules(),
*import_analysis.rules(),
Expand Down
76 changes: 76 additions & 0 deletions src/python/pants/backend/go/goals/check.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,76 @@
# Copyright 2021 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from dataclasses import dataclass
from typing import cast

from pants.backend.go.target_types import GoFirstPartyPackageSourcesField
from pants.backend.go.util_rules.build_pkg import (
BuildGoPackageRequest,
BuildGoPackageTargetRequest,
FallibleBuildGoPackageRequest,
FallibleBuiltGoPackage,
)
from pants.core.goals.check import CheckRequest, CheckResult, CheckResults
from pants.engine.rules import Get, MultiGet, collect_rules, rule
from pants.engine.target import FieldSet
from pants.engine.unions import UnionRule


@dataclass(frozen=True)
class GoCheckFieldSet(FieldSet):
required_fields = (GoFirstPartyPackageSourcesField,)

sources: GoFirstPartyPackageSourcesField


class GoCheckRequest(CheckRequest):
field_set_type = GoCheckFieldSet


@rule
async def check_go(request: GoCheckRequest) -> CheckResults:
build_requests = await MultiGet(
Get(FallibleBuildGoPackageRequest, BuildGoPackageTargetRequest(field_set.address))
for field_set in request.field_sets
)
Comment on lines +33 to +36
Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

This would probably represent a fatal error, right? i.e., something that would prevent anything from compiling? If so, probably fine for it not to be fallible.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

See #13334. We run go list to determine how to build a package, and also its decencies for inference etc. It's important that syntax errors there don't break the entire build.

Copy link
Member

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

In Python, I think that we skip inference in that case, and the effect is that whichever tool you were attempting to use will fail with the parse error instead (I just observed this with mypy reporting a parse error, which after fixing allowed inference to get further). But yea, either way.

Copy link
Contributor Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Exactly. That PR has the same effect. We give up on inference if we fail to run go list

invalid_requests = []
valid_requests = []
for fallible_request in build_requests:
if fallible_request.request is None:
invalid_requests.append(fallible_request)
else:
valid_requests.append(fallible_request.request)

build_results = await MultiGet(
Get(FallibleBuiltGoPackage, BuildGoPackageRequest, request) for request in valid_requests
)

# TODO: Update `build_pkg.py` to use streaming workunits to log compilation results, which has
# the benefit of other contexts like `test.py` using it. Switch this to only preserve the
# exit code.
check_results = [
*(
CheckResult(
result.exit_code,
"",
cast(str, result.stderr),
partition_description=result.import_path,
)
for result in invalid_requests
),
*(
CheckResult(
result.exit_code,
result.stdout or "",
result.stderr or "",
partition_description=result.import_path,
)
for result in build_results
),
]
return CheckResults(check_results, checker_name="go")


def rules():
return [*collect_rules(), UnionRule(CheckRequest, GoCheckRequest)]
85 changes: 85 additions & 0 deletions src/python/pants/backend/go/goals/check_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,85 @@
# Copyright 2021 Pants project contributors (see CONTRIBUTORS.md).
# Licensed under the Apache License, Version 2.0 (see LICENSE).

from __future__ import annotations

from textwrap import dedent

import pytest

from pants.backend.go import target_type_rules
from pants.backend.go.goals import check
from pants.backend.go.goals.check import GoCheckFieldSet, GoCheckRequest
from pants.backend.go.target_types import GoModTarget
from pants.backend.go.util_rules import (
assembly,
build_pkg,
first_party_pkg,
go_mod,
import_analysis,
sdk,
third_party_pkg,
)
from pants.core.goals.check import CheckResult, CheckResults
from pants.engine.addresses import Address
from pants.testutil.rule_runner import QueryRule, RuleRunner


@pytest.fixture
def rule_runner() -> RuleRunner:
rule_runner = RuleRunner(
rules=[
*check.rules(),
*sdk.rules(),
*assembly.rules(),
*build_pkg.rules(),
*import_analysis.rules(),
*go_mod.rules(),
*first_party_pkg.rules(),
*third_party_pkg.rules(),
*target_type_rules.rules(),
QueryRule(CheckResults, [GoCheckRequest]),
],
target_types=[GoModTarget],
)
rule_runner.set_options([], env_inherit={"PATH"})
return rule_runner


def test_check(rule_runner: RuleRunner) -> None:
rule_runner.write_files(
{
"go.mod": dedent(
"""\
module example.com/greeter
go 1.17
"""
),
"bad/f.go": "invalid!!!",
"good/f.go": dedent(
"""\
package greeter

import "fmt"

func Hello() {
fmt.Println("Hello world!")
}
"""
),
"BUILD": "go_mod(name='mod')",
}
)
targets = [
rule_runner.get_target(Address("", target_name="mod", generated_name="./bad")),
rule_runner.get_target(Address("", target_name="mod", generated_name="./good")),
]
results = rule_runner.request(
CheckResults, [GoCheckRequest(GoCheckFieldSet.create(tgt) for tgt in targets)]
).results
assert set(results) == {
CheckResult(0, "", "", "example.com/greeter/good"),
CheckResult(
1, "", "bad/f.go:1:1: expected 'package', found invalid\n", "example.com/greeter/bad"
),
}