diff options
-rw-r--r-- | HACKING.md | 4 | ||||
-rw-r--r-- | README | 8 | ||||
-rw-r--r-- | RELEASING | 4 | ||||
-rwxr-xr-x | configure.py | 5 | ||||
-rw-r--r-- | doc/manual.asciidoc | 9 | ||||
-rw-r--r-- | misc/ninja.vim | 6 | ||||
-rw-r--r-- | misc/packaging/ninja.spec | 2 | ||||
-rw-r--r-- | src/build.cc | 7 | ||||
-rw-r--r-- | src/build_log.h | 2 | ||||
-rw-r--r-- | src/build_test.cc | 8 | ||||
-rw-r--r-- | src/depfile_parser.cc | 58 | ||||
-rw-r--r-- | src/depfile_parser.in.cc | 4 | ||||
-rw-r--r-- | src/depfile_parser_test.cc | 7 | ||||
-rw-r--r-- | src/edit_distance.cc | 38 | ||||
-rw-r--r-- | src/graph_test.cc | 4 | ||||
-rw-r--r-- | src/hash_map.h | 2 | ||||
-rw-r--r-- | src/ninja.cc | 2 | ||||
-rw-r--r-- | src/subprocess-posix.cc | 12 | ||||
-rw-r--r-- | src/subprocess.h | 1 | ||||
-rw-r--r-- | src/subprocess_test.cc | 24 |
20 files changed, 130 insertions, 77 deletions
@@ -53,7 +53,7 @@ obvious exceptions (fixing typos in comments don't need tests). I am very wary of changes that increase the complexity of Ninja (in particular, new build file syntax or command-line flags) or increase -the maintenance burden of Ninja. Ninja is already successfully in use +the maintenance burden of Ninja. Ninja is already successfully used by hundreds of developers for large projects and it already achieves (most of) the goals I set out for it to do. It's probably best to discuss new feature ideas on the mailing list before I shoot down your @@ -74,7 +74,7 @@ build "all" before committing to verify the other source still works! ## Testing performance impact of changes If you have a Chrome build handy, it's a good test case. Otherwise, -[the github downoads page](https://github.com/martine/ninja/downloads) +[the github downoads page](https://github.com/ninja-build/ninja/releases) has a copy of the Chrome build files (and depfiles). You can untar that, then run @@ -1,19 +1,19 @@ Ninja is a small build system with a focus on speed. -http://martine.github.com/ninja/ +http://ninja-build.org/ -See the manual -- http://martine.github.com/ninja/manual.html or +See the manual -- http://ninja-build.org/manual.html or doc/manual.asciidoc included in the distribution -- for background and more details. Binaries for Linux, Mac, and Windows are available at - https://github.com/martine/ninja/releases + https://github.com/ninja-build/ninja/releases Run './ninja -h' for Ninja help. To build your own binary, on many platforms it should be sufficient to just run `./configure.py --bootstrap`; for more details see HACKING.md. (Also read that before making changes to Ninja, as it has advice.) -Installation is not necessary because the only required file is is the +Installation is not necessary because the only required file is the resulting ninja binary. However, to enable features like Bash completion and Emacs and Vim editing modes, some files in misc/ must be copied to appropriate locations. @@ -17,14 +17,14 @@ Push new release branch: Release on github: 1. https://github.com/blog/1547-release-your-software - Add binaries to https://github.com/martine/ninja/releases + Add binaries to https://github.com/ninja-build/ninja/releases Make announcement on mailing list: 1. copy old mail Update website: 1. Make sure your ninja checkout is on the v1.5.0 tag -2. Clone https://github.com/martine/martine.github.io +2. Clone https://github.com/ninja-build/ninja-build.github.io 3. In that repo, `cd ninja && ./update-docs.sh` 4. Update index.html with newest version and link to release notes 5. git commit -m 'run update-docs.sh, 1.5.0 release' diff --git a/configure.py b/configure.py index 1a6d51c..0710ea2 100755 --- a/configure.py +++ b/configure.py @@ -83,7 +83,7 @@ class Platform(object): stdout=subprocess.PIPE, stderr=subprocess.PIPE) out, err = popen.communicate() - return '/FS ' in str(out) + return b'/FS ' in out def is_windows(self): return self.is_mingw() or self.is_msvc() @@ -334,7 +334,8 @@ else: cflags += ['-O2', '-DNDEBUG'] try: proc = subprocess.Popen( - [CXX, '-fdiagnostics-color', '-c', '-x', 'c++', '/dev/null'], + [CXX, '-fdiagnostics-color', '-c', '-x', 'c++', '/dev/null', + '-o', '/dev/null'], stdout=open(os.devnull, 'wb'), stderr=subprocess.STDOUT) if proc.wait() == 0: cflags += ['-fdiagnostics-color'] diff --git a/doc/manual.asciidoc b/doc/manual.asciidoc index 003c71e..3c193f1 100644 --- a/doc/manual.asciidoc +++ b/doc/manual.asciidoc @@ -1,6 +1,5 @@ Ninja ===== -Evan Martin <martine@danga.com> Introduction @@ -598,6 +597,11 @@ rule cc command = cl /showIncludes -c $in /Fo$out ---- +If the include directory directives are using absolute paths, your depfile +may result in a mixture of relative and absolute paths. Paths used by other +build rules need to match exactly. Therefore, it is recommended to use +relative paths in these cases. + [[ref_pool]] Pools ~~~~~ @@ -889,6 +893,9 @@ header file before starting a subsequent compilation step. (Once the header is used in compilation, a generated dependency file will then express the implicit dependency.) +File paths are compared as is, which means that an absolute path and a +relative path, pointing to the same file, are considered different by Ninja. + Variable expansion ~~~~~~~~~~~~~~~~~~ diff --git a/misc/ninja.vim b/misc/ninja.vim index f34588f..190d9ce 100644 --- a/misc/ninja.vim +++ b/misc/ninja.vim @@ -1,6 +1,6 @@ " ninja build file syntax. " Language: ninja build file as described at -" http://martine.github.com/ninja/manual.html +" http://ninja-build.org/manual.html " Version: 1.4 " Last Change: 2014/05/13 " Maintainer: Nicolas Weber <nicolasweber@gmx.de> @@ -9,8 +9,8 @@ " upstream. " ninja lexer and parser are at -" https://github.com/martine/ninja/blob/master/src/lexer.in.cc -" https://github.com/martine/ninja/blob/master/src/manifest_parser.cc +" https://github.com/ninja-build/ninja/blob/master/src/lexer.in.cc +" https://github.com/ninja-build/ninja/blob/master/src/manifest_parser.cc if exists("b:current_syntax") finish diff --git a/misc/packaging/ninja.spec b/misc/packaging/ninja.spec index fa244a6..05f5a07 100644 --- a/misc/packaging/ninja.spec +++ b/misc/packaging/ninja.spec @@ -4,7 +4,7 @@ Version: %{ver} Release: %{rel}%{?dist} Group: Development/Tools License: Apache 2.0 -URL: https://github.com/martine/ninja +URL: https://github.com/ninja-build/ninja Source0: %{name}-%{version}-%{rel}.tar.gz BuildRoot: %{_tmppath}/%{name}-%{version}-%{rel} diff --git a/src/build.cc b/src/build.cc index e4820d0..0e9a399 100644 --- a/src/build.cc +++ b/src/build.cc @@ -96,7 +96,8 @@ void BuildStatus::BuildEdgeStarted(Edge* edge) { running_edges_.insert(make_pair(edge, start_time)); ++started_edges_; - PrintStatus(edge); + if (edge->use_console() || printer_.is_smart_terminal()) + PrintStatus(edge); if (edge->use_console()) printer_.SetConsoleLocked(true); @@ -121,7 +122,7 @@ void BuildStatus::BuildEdgeFinished(Edge* edge, if (config_.verbosity == BuildConfig::QUIET) return; - if (!edge->use_console() && printer_.is_smart_terminal()) + if (!edge->use_console()) PrintStatus(edge); // Print the command that is spewing before printing its output. @@ -362,7 +363,7 @@ void Plan::ScheduleWork(Edge* edge) { if (pool->ShouldDelayEdge()) { // The graph is not completely clean. Some Nodes have duplicate Out edges. // We need to explicitly ignore these here, otherwise their work will get - // scheduled twice (see https://github.com/martine/ninja/pull/519) + // scheduled twice (see https://github.com/ninja-build/ninja/pull/519) if (ready_.count(edge)) { return; } diff --git a/src/build_log.h b/src/build_log.h index fe81a85..785961e 100644 --- a/src/build_log.h +++ b/src/build_log.h @@ -27,7 +27,7 @@ struct Edge; /// Can answer questions about the manifest for the BuildLog. struct BuildLogUser { - /// Return if a given output no longer part of the build manifest. + /// Return if a given output is no longer part of the build manifest. /// This is only called during recompaction and doesn't have to be fast. virtual bool IsPathDead(StringPiece s) const = 0; }; diff --git a/src/build_test.cc b/src/build_test.cc index 52a17c9..20fb664 100644 --- a/src/build_test.cc +++ b/src/build_test.cc @@ -718,7 +718,7 @@ TEST_F(BuildTest, TwoOutputs) { } // Test case from -// https://github.com/martine/ninja/issues/148 +// https://github.com/ninja-build/ninja/issues/148 TEST_F(BuildTest, MultiOutIn) { ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "rule touch\n" @@ -1299,7 +1299,7 @@ TEST_F(BuildWithLogTest, RestatSingleDependentOutputDirty) { } // Test scenario, in which an input file is removed, but output isn't changed -// https://github.com/martine/ninja/issues/295 +// https://github.com/ninja-build/ninja/issues/295 TEST_F(BuildWithLogTest, RestatMissingInput) { ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "rule true\n" @@ -2047,7 +2047,7 @@ TEST_F(BuildWithDepsLogTest, DepFileDepsLogCanonicalize) { #endif /// Check that a restat rule doesn't clear an edge if the depfile is missing. -/// Follows from: https://github.com/martine/ninja/issues/603 +/// Follows from: https://github.com/ninja-build/ninja/issues/603 TEST_F(BuildTest, RestatMissingDepfile) { const char* manifest = "rule true\n" @@ -2071,7 +2071,7 @@ const char* manifest = } /// Check that a restat rule doesn't clear an edge if the deps are missing. -/// https://github.com/martine/ninja/issues/603 +/// https://github.com/ninja-build/ninja/issues/603 TEST_F(BuildWithDepsLogTest, RestatMissingDepfileDepslog) { string err; const char* manifest = diff --git a/src/depfile_parser.cc b/src/depfile_parser.cc index 7268f31..7cee892 100644 --- a/src/depfile_parser.cc +++ b/src/depfile_parser.cc @@ -47,7 +47,7 @@ bool DepfileParser::Parse(string* content, string* err) { const char* start = in; { - char yych; + unsigned char yych; static const unsigned char yybm[] = { 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, 0, @@ -65,22 +65,22 @@ bool DepfileParser::Parse(string* content, string* err) { 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 128, 0, 128, 128, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, - 0, 0, 0, 0, 0, 0, 0, 0, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, + 128, 128, 128, 128, 128, 128, 128, 128, }; yych = *in; @@ -106,24 +106,28 @@ bool DepfileParser::Parse(string* content, string* err) { } } } else { - if (yych <= '^') { - if (yych <= 'Z') { + if (yych <= '_') { + if (yych <= '[') { if (yych <= '?') goto yy9; - goto yy5; + if (yych <= 'Z') goto yy5; + goto yy9; } else { - if (yych != '\\') goto yy9; + if (yych <= '\\') goto yy2; + if (yych <= '^') goto yy9; + goto yy5; } } else { - if (yych <= '{') { - if (yych == '`') goto yy9; - goto yy5; - } else { - if (yych <= '|') goto yy9; - if (yych <= '~') goto yy5; + if (yych <= '|') { + if (yych <= '`') goto yy9; + if (yych <= '{') goto yy5; goto yy9; + } else { + if (yych == 0x7F) goto yy9; + goto yy5; } } } +yy2: ++in; if ((yych = *in) <= '"') { if (yych <= '\f') { diff --git a/src/depfile_parser.in.cc b/src/depfile_parser.in.cc index deaee5b..98c1621 100644 --- a/src/depfile_parser.in.cc +++ b/src/depfile_parser.in.cc @@ -45,7 +45,7 @@ bool DepfileParser::Parse(string* content, string* err) { // start: beginning of the current parsed span. const char* start = in; /*!re2c - re2c:define:YYCTYPE = "char"; + re2c:define:YYCTYPE = "unsigned char"; re2c:define:YYCURSOR = in; re2c:define:YYLIMIT = end; @@ -73,7 +73,7 @@ bool DepfileParser::Parse(string* content, string* err) { *out++ = yych; continue; } - [a-zA-Z0-9+,/_:.~()}{@=!-]+ { + [a-zA-Z0-9+,/_:.~()}{@=!\x80-\xFF-]+ { // Got a span of plain text. int len = (int)(in - start); // Need to shift it over if we're overwriting backslashes. diff --git a/src/depfile_parser_test.cc b/src/depfile_parser_test.cc index fe9424a..ee798f8 100644 --- a/src/depfile_parser_test.cc +++ b/src/depfile_parser_test.cc @@ -121,18 +121,21 @@ TEST_F(DepfileParserTest, SpecialChars) { EXPECT_TRUE(Parse( "C:/Program\\ Files\\ (x86)/Microsoft\\ crtdefs.h: \n" " en@quot.header~ t+t-x!=1 \n" -" openldap/slapd.d/cn=config/cn=schema/cn={0}core.ldif", +" openldap/slapd.d/cn=config/cn=schema/cn={0}core.ldif\n" +" Fu\303\244ball", &err)); ASSERT_EQ("", err); EXPECT_EQ("C:/Program Files (x86)/Microsoft crtdefs.h", parser_.out_.AsString()); - ASSERT_EQ(3u, parser_.ins_.size()); + ASSERT_EQ(4u, parser_.ins_.size()); EXPECT_EQ("en@quot.header~", parser_.ins_[0].AsString()); EXPECT_EQ("t+t-x!=1", parser_.ins_[1].AsString()); EXPECT_EQ("openldap/slapd.d/cn=config/cn=schema/cn={0}core.ldif", parser_.ins_[2].AsString()); + EXPECT_EQ("Fu\303\244ball", + parser_.ins_[3].AsString()); } TEST_F(DepfileParserTest, UnifyMultipleOutputs) { diff --git a/src/edit_distance.cc b/src/edit_distance.cc index a6719d3..3bb62b8 100644 --- a/src/edit_distance.cc +++ b/src/edit_distance.cc @@ -28,40 +28,42 @@ int EditDistance(const StringPiece& s1, // http://en.wikipedia.org/wiki/Levenshtein_distance // // Although the algorithm is typically described using an m x n - // array, only two rows are used at a time, so this implementation - // just keeps two separate vectors for those two rows. + // array, only one row plus one element are used at a time, so this + // implementation just keeps one vector for the row. To update one entry, + // only the entries to the left, top, and top-left are needed. The left + // entry is in row[x-1], the top entry is what's in row[x] from the last + // iteration, and the top-left entry is stored in previous. int m = s1.len_; int n = s2.len_; - vector<int> previous(n + 1); - vector<int> current(n + 1); - - for (int i = 0; i <= n; ++i) - previous[i] = i; + vector<int> row(n + 1); + for (int i = 1; i <= n; ++i) + row[i] = i; for (int y = 1; y <= m; ++y) { - current[0] = y; - int best_this_row = current[0]; + row[0] = y; + int best_this_row = row[0]; + int previous = y - 1; for (int x = 1; x <= n; ++x) { + int old_row = row[x]; if (allow_replacements) { - current[x] = min(previous[x-1] + (s1.str_[y-1] == s2.str_[x-1] ? 0 : 1), - min(current[x-1], previous[x])+1); + row[x] = min(previous + (s1.str_[y - 1] == s2.str_[x - 1] ? 0 : 1), + min(row[x - 1], row[x]) + 1); } else { - if (s1.str_[y-1] == s2.str_[x-1]) - current[x] = previous[x-1]; + if (s1.str_[y - 1] == s2.str_[x - 1]) + row[x] = previous; else - current[x] = min(current[x-1], previous[x]) + 1; + row[x] = min(row[x - 1], row[x]) + 1; } - best_this_row = min(best_this_row, current[x]); + previous = old_row; + best_this_row = min(best_this_row, row[x]); } if (max_edit_distance && best_this_row > max_edit_distance) return max_edit_distance + 1; - - current.swap(previous); } - return previous[n]; + return row[n]; } diff --git a/src/graph_test.cc b/src/graph_test.cc index e41f5cc..44be8a5 100644 --- a/src/graph_test.cc +++ b/src/graph_test.cc @@ -153,7 +153,7 @@ TEST_F(GraphTest, VarInOutPathEscaping) { #endif } -// Regression test for https://github.com/martine/ninja/issues/380 +// Regression test for https://github.com/ninja-build/ninja/issues/380 TEST_F(GraphTest, DepfileWithCanonicalizablePath) { ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "rule catdep\n" @@ -172,7 +172,7 @@ TEST_F(GraphTest, DepfileWithCanonicalizablePath) { EXPECT_FALSE(GetNode("out.o")->dirty()); } -// Regression test for https://github.com/martine/ninja/issues/404 +// Regression test for https://github.com/ninja-build/ninja/issues/404 TEST_F(GraphTest, DepfileRemoved) { ASSERT_NO_FATAL_FAILURE(AssertParse(&state_, "rule catdep\n" diff --git a/src/hash_map.h b/src/hash_map.h index abdba92..a91aeb9 100644 --- a/src/hash_map.h +++ b/src/hash_map.h @@ -76,7 +76,7 @@ struct StringPieceCmp : public hash_compare<StringPiece> { return MurmurHash2(key.str_, key.len_); } bool operator()(const StringPiece& a, const StringPiece& b) const { - int cmp = strncmp(a.str_, b.str_, min(a.len_, b.len_)); + int cmp = memcmp(a.str_, b.str_, min(a.len_, b.len_)); if (cmp < 0) { return true; } else if (cmp > 0) { diff --git a/src/ninja.cc b/src/ninja.cc index f71f6dc..21dede6 100644 --- a/src/ninja.cc +++ b/src/ninja.cc @@ -257,7 +257,7 @@ bool NinjaMain::RebuildManifest(const char* input_file, string* err) { // Even if the manifest was cleaned by a restat rule, claim that it was // rebuilt. Not doing so can lead to crashes, see - // https://github.com/martine/ninja/issues/874 + // https://github.com/ninja-build/ninja/issues/874 return builder.Build(err); } diff --git a/src/subprocess-posix.cc b/src/subprocess-posix.cc index f3baec2..2ddc709 100644 --- a/src/subprocess-posix.cc +++ b/src/subprocess-posix.cc @@ -64,6 +64,8 @@ bool Subprocess::Start(SubprocessSet* set, const string& command) { break; if (sigaction(SIGTERM, &set->old_term_act_, 0) < 0) break; + if (sigaction(SIGHUP, &set->old_hup_act_, 0) < 0) + break; if (sigprocmask(SIG_SETMASK, &set->old_mask_, 0) < 0) break; @@ -136,7 +138,8 @@ ExitStatus Subprocess::Finish() { if (exit == 0) return ExitSuccess; } else if (WIFSIGNALED(status)) { - if (WTERMSIG(status) == SIGINT || WTERMSIG(status) == SIGTERM) + if (WTERMSIG(status) == SIGINT || WTERMSIG(status) == SIGTERM + || WTERMSIG(status) == SIGHUP) return ExitInterrupted; } return ExitFailure; @@ -167,6 +170,8 @@ void SubprocessSet::HandlePendingInterruption() { interrupted_ = SIGINT; else if (sigismember(&pending, SIGTERM)) interrupted_ = SIGTERM; + else if (sigismember(&pending, SIGHUP)) + interrupted_ = SIGHUP; } SubprocessSet::SubprocessSet() { @@ -174,6 +179,7 @@ SubprocessSet::SubprocessSet() { sigemptyset(&set); sigaddset(&set, SIGINT); sigaddset(&set, SIGTERM); + sigaddset(&set, SIGHUP); if (sigprocmask(SIG_BLOCK, &set, &old_mask_) < 0) Fatal("sigprocmask: %s", strerror(errno)); @@ -184,6 +190,8 @@ SubprocessSet::SubprocessSet() { Fatal("sigaction: %s", strerror(errno)); if (sigaction(SIGTERM, &act, &old_term_act_) < 0) Fatal("sigaction: %s", strerror(errno)); + if (sigaction(SIGHUP, &act, &old_hup_act_) < 0) + Fatal("sigaction: %s", strerror(errno)); } SubprocessSet::~SubprocessSet() { @@ -193,6 +201,8 @@ SubprocessSet::~SubprocessSet() { Fatal("sigaction: %s", strerror(errno)); if (sigaction(SIGTERM, &old_term_act_, 0) < 0) Fatal("sigaction: %s", strerror(errno)); + if (sigaction(SIGHUP, &old_hup_act_, 0) < 0) + Fatal("sigaction: %s", strerror(errno)); if (sigprocmask(SIG_SETMASK, &old_mask_, 0) < 0) Fatal("sigprocmask: %s", strerror(errno)); } diff --git a/src/subprocess.h b/src/subprocess.h index a001fc9..51f40b2 100644 --- a/src/subprocess.h +++ b/src/subprocess.h @@ -98,6 +98,7 @@ struct SubprocessSet { struct sigaction old_int_act_; struct sigaction old_term_act_; + struct sigaction old_hup_act_; sigset_t old_mask_; #endif }; diff --git a/src/subprocess_test.cc b/src/subprocess_test.cc index 07cc52f..066bbb7 100644 --- a/src/subprocess_test.cc +++ b/src/subprocess_test.cc @@ -122,6 +122,30 @@ TEST_F(SubprocessTest, InterruptParentWithSigTerm) { ASSERT_FALSE("We should have been interrupted"); } +TEST_F(SubprocessTest, InterruptChildWithSigHup) { + Subprocess* subproc = subprocs_.Add("kill -HUP $$"); + ASSERT_NE((Subprocess *) 0, subproc); + + while (!subproc->Done()) { + subprocs_.DoWork(); + } + + EXPECT_EQ(ExitInterrupted, subproc->Finish()); +} + +TEST_F(SubprocessTest, InterruptParentWithSigHup) { + Subprocess* subproc = subprocs_.Add("kill -HUP $PPID ; sleep 1"); + ASSERT_NE((Subprocess *) 0, subproc); + + while (!subproc->Done()) { + bool interrupted = subprocs_.DoWork(); + if (interrupted) + return; + } + + ASSERT_FALSE("We should have been interrupted"); +} + // A shell command to check if the current process is connected to a terminal. // This is different from having stdin/stdout/stderr be a terminal. (For // instance consider the command "yes < /dev/null > /dev/null 2>&1". |