Skip to content

Even better Makefile target parsing#8819

Merged
scheibelp merged 16 commits intospack:developfrom
adamjstewart:fixes/make-check
Aug 20, 2018
Merged

Even better Makefile target parsing#8819
scheibelp merged 16 commits intospack:developfrom
adamjstewart:fixes/make-check

Conversation

@adamjstewart
Copy link
Copy Markdown
Member

@adamjstewart adamjstewart commented Jul 28, 2018

This is a continuation of #8223.

In #8818, @SteVwonder discovered a bug in my implementation. For reasons I still don't fully understand, make -q does not work as documented. This can easily be reproduced with the libsigsegv package, which does not have any dependencies. Outside of Spack, we can see that make check works fine, but make -q check crashes:

$ make -q check
Making check in src
Making check in tests
/Applications/Xcode.app/Contents/Developer/usr/bin/make  check-TESTS
make[1]: *** [check-am] Error 1
make: *** [check-recursive] Error 1
$ echo $?
2
$ make check
Making check in src
make[1]: Nothing to be done for `check'.
Making check in tests
/Applications/Xcode.app/Contents/Developer/usr/bin/make  check-TESTS
PASS: sigsegv1
PASS: sigsegv2
PASS: sigsegv3
PASS: stackoverflow1
PASS: stackoverflow2
============================================================================
Testsuite summary for 
============================================================================
# TOTAL: 5
# PASS:  5
# SKIP:  0
# XFAIL: 0
# FAIL:  0
# XPASS: 0
# ERROR: 0
============================================================================

Now please type 'make install' to install the package.
$ echo $?
0

The error code for make -q was also unreliable, it only worked for GNU Make, not BSD Make.

The new implementation ignores the exit code and instead parses STDERR for any indications that the target does not exist. This works for both GNU Make and BSD Make.

I switched from make -q to make -n. It works similarly to make -q (it doesn't actually run anything, just prints what would be run). The only difference is that make -q doesn't print an error message when using BSD Make, but make -n does.

@SteVwonder would you mind testing this with #8818?

Closes #8604

@adamjstewart adamjstewart added tests General test capability(ies) makefile labels Jul 28, 2018
@SteVwonder
Copy link
Copy Markdown
Member

@SteVwonder would you mind testing this with #8818?

I gave it a whirl. This is the output I could see:

==> RUN-TESTS: build-time tests [check]
==> 'make' '-j56' '-n' 'test'
==> Target 'test' not found in Makefile
==> 'make' '-j56' '-n' 'check'
==> Target 'check' not found in Makefile
==> Executing phase: 'install'
==> 'make' '-j56' 'install'

I'm digging deeper now.

@SteVwonder
Copy link
Copy Markdown
Member

Output from manually running it:

# herbein1 at hype2 in /usr/workspace/wsb/herbein1/spack_toss3/var/spack/stage/flux-core-master-ax7lswmzn2koloklj5zinph2efc2acqr/flux-core/spack-build on git:master ✖︎ [21:04:48]
→ make -n check
fail=; \
if (target_option=k; case ${target_option-} in ?) ;; *) echo "am__make_running_with_option: internal error: invalid" "target option '${target_option-}' specified" >&2; exit 1;; esac; has_opt=no; sane_makeflags=$MAKEFLAGS; if { if test -z '
0'; then false; elif test -n ''; then true; elif test -n '3.82' && test -n '/tmp/herbein1/spack-stage/spack-stage-lhoEHx/flux-core/spack-build'; then true; else false; fi; }; then sane_makeflags=$MFLAGS; else case $MAKEFLAGS in *\\[\ \   ]
*) bs=\\; sane_makeflags=`printf '%s\n' "$MAKEFLAGS" | sed "s/$bs$bs[$bs $bs    ]*//g"`;; esac; fi; skip_next=no; strip_trailopt () { flg=`printf '%s\n' "$flg" | sed "s/$1.*$//"`; }; for flg in $sane_makeflags; do test $skip_next = yes &&
{ skip_next=no; continue; }; case $flg in *=*|--*) continue;; -*I) strip_trailopt 'I'; skip_next=yes;; -*I?*) strip_trailopt 'I';; -*O) strip_trailopt 'O'; skip_next=yes;; -*O?*) strip_trailopt 'O';; -*l) strip_trailopt 'l'; skip_next=yes;
; -*l?*) strip_trailopt 'l';; -[dEDm]) skip_next=yes;; -[JT]) skip_next=yes;; esac; case $flg in *$target_option*) has_opt=yes; break;; esac; done; test $has_opt = yes); then \
  failcom='fail=yes'; \
else \
  failcom='exit 1'; \
fi; \
dot_seen=no; \
target=`echo check-recursive | sed s/-recursive//`; \
case "check-recursive" in \
  distclean-* | maintainer-clean-*) list='. src doc etc t' ;; \
  *) list='. src doc etc t' ;; \
esac; \
for subdir in $list; do \
  echo "Making $target in $subdir"; \
  if test "$subdir" = "."; then \
    dot_seen=yes; \
    local_target="$target-am"; \
  else \
    local_target="$target"; \
  fi; \
  (CDPATH="${ZSH_VERSION+.}:" && cd $subdir && make  $local_target) \
  || eval $failcom; \
done; \
if test "$dot_seen" = "no"; then \
  make  "$target-am" || exit 1; \
fi; test -z "$fail"
Making check in .
make[1]: Entering directory `/tmp/herbein1/spack-stage/spack-stage-lhoEHx/flux-core/spack-build'
make  check-local
make[2]: Entering directory `/tmp/herbein1/spack-stage/spack-stage-lhoEHx/flux-core/spack-build'

<snip>

make[4]: Leaving directory `/tmp/herbein1/spack-stage/spack-stage-lhoEHx/flux-core/spack-build/src/common/libtap'
make[3]: Leaving directory `/tmp/herbein1/spack-stage/spack-stage-lhoEHx/flux-core/spack-build/src/common/libtap'
Making check in libev
make[3]: Entering directory `/tmp/herbein1/spack-stage/spack-stage-lhoEHx/flux-core/spack-build/src/common/libev'
make[3]: Nothing to be done for `check'.
make[3]: Leaving directory `/tmp/herbein1/spack-stage/spack-stage-lhoEHx/flux-core/spack-build/src/common/libev'
Making check in libminilzo
make[3]: Entering directory `/tmp/herbein1/spack-stage/spack-stage-lhoEHx/flux-core/spack-build/src/common/libminilzo'
make  test_mini.t
make[4]: Entering directory `/tmp/herbein1/spack-stage/spack-stage-lhoEHx/flux-core/spack-build/src/common/libminilzo'
/usr/bin/mkdir -p test
: > test/.dirstamp
/usr/bin/mkdir -p test/.deps
: > test/.deps/.dirstamp
echo "  CC      " test/mini.o;depbase=`echo test/mini.o | sed 's|[^/]*$|.deps/&|;s|\.o$||'`;\
/usr/workspace/wsb/herbein1/spack_toss3/lib/spack/env/gcc/gcc -std=gnu99 -DHAVE_CONFIG_H -I. -I/usr/workspace/wsb/herbein1/spack_toss3/var/spack/stage/flux-core-master-ax7lswmzn2koloklj5zinph2efc2acqr/flux-core/src/common/libminilzo -I../.
./../config  -I/usr/workspace/wsb/herbein1/spack_toss3/var/spack/stage/flux-core-master-ax7lswmzn2koloklj5zinph2efc2acqr/flux-core -I/usr/workspace/wsb/herbein1/spack_toss3/var/spack/stage/flux-core-master-ax7lswmzn2koloklj5zinph2efc2acqr/
flux-core/src/include -Wno-strict-aliasing -Wno-error=strict-aliasing  -Wall -Werror -Werror=missing-field-initializers -Wno-strict-aliasing -Wno-error=deprecated-declarations  -g -O2 -MT test/mini.o -MD -MP -MF $depbase.Tpo -c -o test/min
i.o /usr/workspace/wsb/herbein1/spack_toss3/var/spack/stage/flux-core-master-ax7lswmzn2koloklj5zinph2efc2acqr/flux-core/src/common/libminilzo/test/mini.c &&\
mv -f $depbase.Tpo $depbase.Po
make[4]: *** No rule to make target `../../../src/common/libtap/libtap.la', needed by `test_mini.t'.  Stop.
make[4]: Leaving directory `/tmp/herbein1/spack-stage/spack-stage-lhoEHx/flux-core/spack-build/src/common/libminilzo'
make[3]: *** [check-am] Error 2
make[3]: Leaving directory `/tmp/herbein1/spack-stage/spack-stage-lhoEHx/flux-core/spack-build/src/common/libminilzo'
make[2]: *** [check-recursive] Error 1
make[2]: Leaving directory `/tmp/herbein1/spack-stage/spack-stage-lhoEHx/flux-core/spack-build/src/common'
make[1]: *** [check-recursive] Error 1
make[1]: Leaving directory `/tmp/herbein1/spack-stage/spack-stage-lhoEHx/flux-core/spack-build/src'
make: *** [check-recursive] Error 1

@SteVwonder
Copy link
Copy Markdown
Member

Just a heads up, it appears that -n might still execute some receipes. Per the online manual:

‘-n’
‘--just-print’
‘--dry-run’
‘--recon’

    “No-op”. Causes make to print the recipes that are needed to make the targets up to date, but not actually execute them. Note that some recipes are still executed, even with this flag (see How the MAKE Variable Works). Also any recipes needed to update included makefiles are still executed (see How Makefiles Are Remade).

@SteVwonder
Copy link
Copy Markdown
Member

It seems that most of the trouble with flux-core/sched's Makefile's comes from the subdirectories and the recursive nature of the calls. Potentially stupid idea alert: what if we just use grep on the top-level Makefile?

 herbein1 at hype2 in /usr/workspace/wsb/herbein1/spack_toss3/var/spack/stage/flux-core-master-ax7lswmzn2koloklj5zinph2efc2acqr/flux-core/spack-build on git:master ✖︎ [21:10:47]
→ grep -q "^check:" ./Makefile && echo "check target exists" || echo "NO CHECK TARGET"
check target exists

@adamjstewart
Copy link
Copy Markdown
Member Author

See #8223 for why grep wasn't sufficient. If we can come up with a better regex, that may be the last resort.

@adamjstewart
Copy link
Copy Markdown
Member Author

@SteVwonder Can you try the latest commit? That should fix the problem you were seeing. I still don't like the fact that it errors out though.

We need to decide how worrying the potential for make -n to run the Makefile is. If this is a big concern, we will need to go back to regexes. For example, if we are looking for the check target, we would need to be able to capture:

check:
check :
test check:
check test:
foo bar check baz:

but not:

installcheck:
checkinstall:

@SteVwonder
Copy link
Copy Markdown
Member

@adamjstewart: your latest commit seems to work:

==> RUN-TESTS: build-time tests [check]
==> 'make' '-j56' '-n' 'test'
==> Target 'test' not found in Makefile
==> 'make' '-j56' '-n' 'check'
==> 'make' '-j56' 'check'
Making check in .
make[1]: Entering directory `/tmp/herbein1/spack-stage/spack-stage-Blgqv1/flux-core/spack-build'
make  check-local
make[2]: Entering directory `/tmp/herbein1/spack-stage/spack-stage-Blgqv1/flux-core/spack-build'
Making all in .
make[3]: Entering directory `/tmp/herbein1/spack-stage/spack-stage-Blgqv1/flux-core/spack-build'
make[3]: Nothing to be done for `all-am'.
make[3]: Leaving directory `/tmp/herbein1/spack-stage/spack-stage-Blgqv1/flux-core/spack-build'
Making all in src
make[3]: Entering directory `/tmp/herbein1/spack-stage/spack-stage-Blgqv1/flux-core/spack-build/src'
Making all in common
make[4]: Entering directory `/tmp/herbein1/spack-stage/spack-stage-Blgqv1/flux-core/spack-build/src/common'
Making all in libtap
make[5]: Entering directory `/tmp/herbein1/spack-stage/spack-stage-Blgqv1/flux-core/spack-build/src/common/libtap'
make[5]: Nothing to be done for `all'.
make[5]: Leaving directory `/tmp/herbein1/spack-stage/spack-stage-Blgqv1/flux-core/spack-build/src/common/libtap'
Making all in libev
make[5]: Entering directory `/tmp/herbein1/spack-stage/spack-stage-Blgqv1/flux-core/spack-build/src/common/libev'
make[5]: Nothing to be done for `all'.
make[5]: Leaving directory `/tmp/herbein1/spack-stage/spack-stage-Blgqv1/flux-core/spack-build/src/common/libev'

<snip>

FWIW, ^([a-z]+ )*check( [a-z]+)* *:$ should work for the test cases you provided: https://regex101.com/r/e3LuE4/1

Another potentially terrible idea is to actually parse the Makefile using pyparsing or similar.

@adamjstewart
Copy link
Copy Markdown
Member Author

I might try that regex. I need to find some documentation on allowed target names (I think they can contain uppercase and dashes) but that might be just what I'm looking for. Avoiding accidentally running the Makefile or dealing with different versions of Make sounds nice.

@adamjstewart adamjstewart changed the title Even better Makefile target parsing [WIP] Even better Makefile target parsing Jul 31, 2018
@SteVwonder
Copy link
Copy Markdown
Member

It looks like targets can contain:

We could handle a lot of that via the regex character class [^:], but I'm not sure how we would handle the case of variables expanding to include check:

# herbein1 at auk100.llnl.gov in ~/Repositories/ on git:master ✖︎ [13:37:05]
→ cat Makefile  
TARGETS=check

$(TARGETS):
        echo "RUNNING"

# herbein1 at auk100.llnl.gov in ~/Repositories/ on git:master ✖︎ [13:37:14]
→ make check
echo "RUNNING"
RUNNING

The make -n case does handle the variable expansion though:

# herbein1 at auk100.llnl.gov in ~/Repositories/ on git:master ✖︎ [13:37:25]
→ make -n check
echo "RUNNING"

# herbein1 at auk100.llnl.gov in ~/Repositories/ on git:master ✖︎ [13:41:24]
→ make -n foobar
make: *** No rule to make target `foobar'.  Stop.

@adamjstewart
Copy link
Copy Markdown
Member Author

adamjstewart commented Aug 2, 2018

@SteVwonder So far your regex is up to:

^([^ :]+ )*check( [^ :]+)* *:$

There are two problems with this:

  1. The line doesn't have to end with a colon. We could have a file like:
check: recursive-check

recursive-check:
        the actual build instructions

So we need to drop the $.

  1. We need to avoid some other things, like variable assignment:
check := ${test}

Does this seem sufficient?

^([^ :]+ )*check( [^ :]+)* *:[^=]

I wouldn't think this would match a line ending with a colon, but regex101.com seems to think otherwise.

P.S. This is why I gave up last time. So many corner cases...

If we really want to support the variable expansion stuff, we should just punt and stick with make -n for now and wait until it breaks another package.

@adamjstewart adamjstewart removed the WIP label Aug 2, 2018
@adamjstewart adamjstewart changed the title [WIP] Even better Makefile target parsing Even better Makefile target parsing Aug 2, 2018
@SteVwonder
Copy link
Copy Markdown
Member

@adamjstewart: I'm leaning towards sticking with make -n. The regex is going to be fragile compared to relying on make to spit out the target names. It looks like the main case where make -n still executes recipes is when recursive calls to make are made without using the ${MAKE} variable. Spack only calls make -n after having already run make, correct? So I assume that recipes will just be running for the second time if things actually do get run (resulting in a no-op). The caveat being that I am not sure if there are additional scenarios where make -n causes things to actually execute. And I'm sure there are some really twisted Makefiles out there that violate all these assumptions (in those cases, I guess we just override the check method).

If you want to go with the regex, I can take another look at what you have so far.

@adamjstewart adamjstewart changed the title Even better Makefile target parsing [WIP] Even better Makefile target parsing Aug 18, 2018
@adamjstewart
Copy link
Copy Markdown
Member Author

I added unit tests but they're failing for some reason:

AttributeError: module 'spack.pkg.builtin.mock.mpich' has no attribute 'make'

Any idea how to get this working? @alalazo @tgamblin

@@ -0,0 +1,2 @@
.ninja_deps
.ninja_log
Copy link
Copy Markdown
Member Author

Choose a reason for hiding this comment

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

Running ninja check generates these files. Should I try to clean them up? How do I do that in pytest?

@adamjstewart adamjstewart changed the title [WIP] Even better Makefile target parsing Even better Makefile target parsing Aug 19, 2018
@adamjstewart adamjstewart removed the WIP label Aug 19, 2018
@adamjstewart
Copy link
Copy Markdown
Member Author

Okay, this should be ready to merge now. I added unit tests for every conceivable Makefile or build.ninja we might encounter. It provides a nice boost to our core coverage as well.

Let me know if you want me to clean up those .ninja_* files.

@scheibelp
Copy link
Copy Markdown
Member

Are you still tweaking this? It looked good to me so I was going to merge it

@adamjstewart
Copy link
Copy Markdown
Member Author

I'm done, I was just trying to clean up those pesky Ninja files. If the last commit looks good to you go ahead and merge.

@scheibelp
Copy link
Copy Markdown
Member

It looks good. However, I'm writing a commit message and I'm curious if you added 0222d1e (adding 'all' to the ninja invocation) only to be more explicit - i.e. is the behavior the same with or without 'all'? could it conceivably fail or return something unexpected without 'all'?

@adamjstewart
Copy link
Copy Markdown
Member Author

I added it only to be more explicit. The behavior is identical with or without all. For reference, Ninja's Bash completion script also uses ninja -t targets all.

@scheibelp
Copy link
Copy Markdown
Member

It appears that a couple of the test_negative_make_check tests failed in https://travis-ci.org/spack/spack/jobs/418414627 (for python 3.7) - do you have a notion of why this would have occurred?

@adamjstewart
Copy link
Copy Markdown
Member Author

No idea.

@scheibelp
Copy link
Copy Markdown
Member

OK - I have added an issue on 3.7 test failures at #9028. For now, we allow those to fail so I'll merge this.

@scheibelp scheibelp merged commit 2e8a820 into spack:develop Aug 20, 2018
@adamjstewart adamjstewart deleted the fixes/make-check branch August 20, 2018 21:43
@tgamblin
Copy link
Copy Markdown
Member

@adamjstewart @scheibelp: is develop failing on this PR?

@adamjstewart
Copy link
Copy Markdown
Member Author

Develop seems to be passing? It only fails the Python 3.7 test.

@alalazo
Copy link
Copy Markdown
Member

alalazo commented Aug 22, 2018

Develop seems to be passing? It only fails the Python 3.7 test.

Imo, any test failing on Python 3.7 should be considered a "real" failure. In #8778 I added:

allow_failures:
    - dist: xenial

just because the way to get a worker on Travis with xenial is not yet official, and it may change without notice in the future (for the record using xenial seemed to be the only easy way to run on Python 3.7). See also travis-ci/travis-ci#9815

anderson2981 pushed a commit to anderson2981/spack that referenced this pull request Sep 7, 2018
spack#8223 replaced regex-based makefile target parsing with an invocation of
"make -q". spack#8818 discovered that "make -q" can result in an error for some
packages.

Also, the "make -q" strategy relied on interpreting the error code, which only
worked for GNU Make and not BSD Make (which was deemed acceptable at
the time). As an added bonus, this implementation ignores the exit code and
instead parses STDERR for any indications that the target does not exist; this
works for both GNU Make and BSD Make.

spack#8223 also updated ninja target detection to use "ninja -t targets". This does
not change that behavior but makes it more-explicit with "ninja -t targets all"

This also adds tests for detection of "make" and "ninja" targets.
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

tests General test capability(ies)

Projects

None yet

Development

Successfully merging this pull request may close these issues.

5 participants