Skip to content
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
83 changes: 78 additions & 5 deletions misc/output_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -152,14 +152,18 @@ class Output(unittest.TestCase):
'',
))

def _test_expected_error(self, plan: str, flags: T.Optional[str], expected: str):
def _test_expected_error(self, plan: str, flags: T.Optional[str],expected: str,
*args, exit_code: T.Optional[int]=None, **kwargs)->None:
"""Run Ninja with a given plan and flags, and verify its cooked output against an expected content.
All *args and **kwargs are passed to the `run` function
"""
actual = ''
try:
actual = run(plan, flags, print_err_output=False)
except subprocess.CalledProcessError as err:
actual = err.cooked_output
kwargs['print_err_output'] = False
with self.assertRaises(subprocess.CalledProcessError) as cm:
run(plan, flags, *args, **kwargs)
actual = cm.exception.cooked_output
if exit_code is not None:
self.assertEqual(cm.exception.returncode, exit_code)
self.assertEqual(expected, actual)

def test_issue_1418(self) -> None:
Expand Down Expand Up @@ -281,6 +285,75 @@ def test_issue_2048(self) -> None:
except subprocess.CalledProcessError as err:
self.fail("non-zero exit code with: " + err.output)

def test_pr_2540(self)->None:
py = sys.executable
plan = f'''\
rule CUSTOM_COMMAND
command = $COMMAND

build 124: CUSTOM_COMMAND
COMMAND = {py} -c 'exit(124)'

build 127: CUSTOM_COMMAND
COMMAND = {py} -c 'exit(127)'

build 130: CUSTOM_COMMAND
COMMAND = {py} -c 'exit(130)'

build 137: CUSTOM_COMMAND
COMMAND = {py} -c 'exit(137)'

build success: CUSTOM_COMMAND
COMMAND = sleep 0.3; echo success
'''
# Disable colors
env = default_env.copy()
env['TERM'] = 'dumb'
self._test_expected_error(
plan, '124',
f'''[1/1] {py} -c 'exit(124)'
FAILED: [code=124] 124 \n{py} -c 'exit(124)'
ninja: build stopped: subcommand failed.
''',
exit_code=124, env=env,
)
self._test_expected_error(
plan, '127',
f'''[1/1] {py} -c 'exit(127)'
FAILED: [code=127] 127 \n{py} -c 'exit(127)'
ninja: build stopped: subcommand failed.
''',
exit_code=127, env=env,
)
self._test_expected_error(
plan, '130',
'ninja: build stopped: interrupted by user.\n',
exit_code=130, env=env,
)
self._test_expected_error(
plan, '137',
f'''[1/1] {py} -c 'exit(137)'
FAILED: [code=137] 137 \n{py} -c 'exit(137)'
ninja: build stopped: subcommand failed.
''',
exit_code=137, env=env,
)
self._test_expected_error(
plan, 'non-existent-target',
"ninja: error: unknown target 'non-existent-target'\n",
exit_code=1, env=env,
)
self._test_expected_error(
plan, '-j2 success 127',
f'''[1/2] {py} -c 'exit(127)'
FAILED: [code=127] 127 \n{py} -c 'exit(127)'
[2/2] sleep 0.3; echo success
success
ninja: build stopped: subcommand failed.
''',
exit_code=127, env=env,
)

def test_depfile_directory_creation(self) -> None:
b = BuildDir('''\
rule touch
Expand Down
29 changes: 19 additions & 10 deletions src/build.cc
Original file line number Diff line number Diff line change
Expand Up @@ -34,12 +34,12 @@
#include "depfile_parser.h"
#include "deps_log.h"
#include "disk_interface.h"
#include "exit_status.h"
#include "explanations.h"
#include "graph.h"
#include "metrics.h"
#include "state.h"
#include "status.h"
#include "subprocess.h"
#include "util.h"

using namespace std;
Expand Down Expand Up @@ -687,7 +687,7 @@ bool Builder::AlreadyUpToDate() const {
return !plan_.more_to_do();
}

bool Builder::Build(string* err) {
ExitStatus Builder::Build(string* err) {
assert(!AlreadyUpToDate());
plan_.PrepareQueue();

Expand Down Expand Up @@ -726,14 +726,14 @@ bool Builder::Build(string* err) {
if (!StartEdge(edge, err)) {
Cleanup();
status_->BuildFinished();
return false;
return ExitFailure;
}

if (edge->is_phony()) {
if (!plan_.EdgeFinished(edge, Plan::kEdgeSucceeded, err)) {
Cleanup();
status_->BuildFinished();
return false;
return ExitFailure;
}
} else {
++pending_commands;
Expand All @@ -760,14 +760,16 @@ bool Builder::Build(string* err) {
Cleanup();
status_->BuildFinished();
*err = "interrupted by user";
return false;
return result.status;
}

--pending_commands;
if (!FinishCommand(&result, err)) {
bool command_finished = FinishCommand(&result, err);
SetFailureCode(result.status);
if (!command_finished) {
Cleanup();
status_->BuildFinished();
return false;
return result.status;
}

if (!result.success()) {
Expand All @@ -791,11 +793,11 @@ bool Builder::Build(string* err) {
else
*err = "stuck [this is a bug]";

return false;
return GetExitCode();
}

status_->BuildFinished();
return true;
return ExitSuccess;
}

bool Builder::StartEdge(Edge* edge, string* err) {
Expand Down Expand Up @@ -883,7 +885,7 @@ bool Builder::FinishCommand(CommandRunner::Result* result, string* err) {
running_edges_.erase(it);

status_->BuildEdgeFinished(edge, start_time_millis, end_time_millis,
result->success(), result->output);
result->status, result->output);

// The rest of this function only applies to successful commands.
if (!result->success()) {
Expand Down Expand Up @@ -1036,3 +1038,10 @@ bool Builder::LoadDyndeps(Node* node, string* err) {

return true;
}

void Builder::SetFailureCode(ExitStatus code) {
// ExitSuccess should not overwrite any error
if (code != ExitSuccess) {
exit_code_ = code;
}
}
12 changes: 10 additions & 2 deletions src/build.h
Original file line number Diff line number Diff line change
Expand Up @@ -209,9 +209,9 @@ struct Builder {
/// Returns true if the build targets are already up to date.
bool AlreadyUpToDate() const;

/// Run the build. Returns false on error.
/// Run the build. Returns ExitStatus or the exit code of the last failed job.
/// It is an error to call this function when AlreadyUpToDate() is true.
bool Build(std::string* err);
ExitStatus Build(std::string* err);

bool StartEdge(Edge* edge, std::string* err);

Expand All @@ -233,6 +233,10 @@ struct Builder {
std::unique_ptr<CommandRunner> command_runner_;
Status* status_;

/// Returns ExitStatus or the exit code of the last failed job
/// (doesn't need to be an enum value of ExitStatus)
ExitStatus GetExitCode() const { return exit_code_; }

private:
bool ExtractDeps(CommandRunner::Result* result, const std::string& deps_type,
const std::string& deps_prefix,
Expand All @@ -253,6 +257,10 @@ struct Builder {

DependencyScan scan_;

/// Keep the global exit code for the build
ExitStatus exit_code_ = ExitSuccess;
void SetFailureCode(ExitStatus code);

// Unimplemented copy ctor and operator= ensure we don't copy the auto_ptr.
Builder(const Builder &other); // DO NOT IMPLEMENT
void operator=(const Builder &other); // DO NOT IMPLEMENT
Expand Down
Loading
Loading