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

Python handling Bash/CMake compilation errors #102

Merged
merged 10 commits into from
Mar 4, 2018

Conversation

WetzelVictor
Copy link
Collaborator

This PR is a first fix designed to catch compilation errors when using the C++ feature on PyPHS.


Principle

It relies on a temporary file, stderr, created in the same folder as run.sh.

When running a C++ based simulation, PyPHS execute a shell script that is the interface between Python and the CMake compiler. Every step is logged in stderr. After the compilation step, Python check for the log:

stderr: end

if this line doesn't appear in the stderr file, python raise a RuntimeError, and the last line of the file, which contains the last step that caused the error.


Implementation

Shell script

pyphs/numerics/cpp/simu2cpp.py

  • the set -e command affects the behavior of the script: if an error is encountered, bash stops the execution of the script.
  • The potentially previous stderr file is removed (if fi control structure)
  • Every step is logged in this file: echo "stderr: step n - doing stuff" > stderr
  • Confirmation step: inserting stderr: end at the end of the file

Error checking

pyphs/numerics/simulations/simulation.py

  • Python reads the file line by line
  • Removes the unnecessary characters
  • Check if stderr: end is in the list
  • Raise an error if not, with the last line of the stderr

Drawbacks

This implementation is not the best, but it was the one that required the least amount of time. Some ideas:

  • Is the RuntimeError the correct exception to raise?
  • Use the actual stdout or stderr created by the execution of bash file. I tried to work with that, but couldn't figure it out.
  • I am not sure about the flexibility of this method

…s the script goes through. If the script sctops itself, the last line < stderr: end > will not show up. Therefore, Python can check if this message exists in the file. If not, he throws a RunTime error, and indicates which step did not worked (last line in the file)
…all the steps that the run.sh performs with CMake. Two cases: if everything goes well, the error file contains the string <stderr: end> and is removed; if somethng goes wrong, python throws an error, and tell which cmake operation is causing the prolem
@codecov-io
Copy link

codecov-io commented Feb 28, 2018

Codecov Report

Merging #102 into master will decrease coverage by 0.19%.
The diff coverage is 0%.

Impacted file tree graph

@@            Coverage Diff            @@
##           master     #102     +/-   ##
=========================================
- Coverage   82.54%   82.34%   -0.2%     
=========================================
  Files         162      162             
  Lines        7540     7558     +18     
=========================================
  Hits         6224     6224             
- Misses       1316     1334     +18
Impacted Files Coverage Δ
pyphs/numerics/simulations/simulation.py 62.89% <0%> (-5.58%) ⬇️

Continue to review full report at Codecov.

Legend - Click here to learn more
Δ = absolute <relative> (impact), ø = not affected, ? = missing data
Powered by Codecov. Last update 16b6f45...50def5d. Read the comment docs.

@FabricioS
Copy link
Collaborator

FabricioS commented Feb 28, 2018

Hi all,
I admit I can understand the purpose of the shell script during the development process, but I am more reluctant about this generated script once the debug phase is over as it mixes build and execution of the simulation code, has no error checking, etc..
We can gain advantage from the subprocess module that has much to offer. For instance, this code:

import os
import subprocess as sp

project_name = 'dampedosc'
kwsp = {
    "cwd": os.path.join(project_name, project_name),
    "stderr": sp.PIPE,
    "check": True,
    "shell": True,
    "universal_newlines": True
}
with open(os.path.join(project_name, project_name, "build.log"), 'w') as fid:
    kwsp['stdout'] = fid
    p = sp.run('cmake . -Bbuild', **kwsp)
    p = sp.run('cmake --build build -- -j3', **kwsp)

kwsp['stdout'] = sp.PIPE
p = sp.run('./bin/%s' % project_name, **kwsp)
print(p.stdout)

raise an error if an error occurs during the build (cmake returning a non zero error code) or the simulation. In this code, the output (stdout and stderr) of the build are logged in the build.log file while the output of the simulation appears in the python interpreter. Many possibilities are available and they greatly simplifies the workflow with respect to this pull request.
Best regards

@WetzelVictor
Copy link
Collaborator Author

Hi,

This implementation seems to be a more robust and flexible solution, that takes advantage of everything the subprocess has to offer.

It will also, in my own opinion, simplify the code inside PyPHS and eliminate a "blind" interface between python and cmake.

@WetzelVictor WetzelVictor changed the title Python handling Bash/CMake compilation errors with a temp file Python handling Bash/CMake compilation errors Mar 2, 2018
@WetzelVictor
Copy link
Collaborator Author

WetzelVictor commented Mar 2, 2018

Hi everyone,

I took @FabricioS example code, and implemented it in the _process_cpp() method, inside simulation.py. It catches compilations error, as excepted. (commits
b26d034 and 3fa0d93).

It needs to be tested on Windows. It seems that the option shell has a different behavior. It might be necessary to add a condition like:

if sys.platform.startswith('win'):
    sp_config["shell"] = True
else:
    sp_config["shell"] = False 

Thoughts

  • I was wondering if it is useful to print the build log file at the end of the execution? Or setting it as an option?
  • Also, what do you think about removing or renaming the old simulation exec file before recompiling? It could help avoiding using an old cpp implementation.

What are your insights on this?

Best regards,

Copy link
Collaborator

@FabricioS FabricioS left a comment

Choose a reason for hiding this comment

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

This Pull Request seems to integrate things for the other pull request (#100).
Is this intended ?
See also inline comments about how to explicitly report the build errors to users


# build simu.cpp
# build simu.cpp
Copy link
Collaborator

Choose a reason for hiding this comment

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

Be careful with indentation. This two-lines change can be reverted

os.remove('stderr')
# Commands
cmd = {
'Bbuild': '%s . -Bbuild' % self.config['cmake'],
Copy link
Collaborator

Choose a reason for hiding this comment

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

You can even be more explicit in the keys of the cmd dictionary. For example:
Bbuild -> build_tree
build -> compile_src

# Perform build
with open(os.path.join(sp_config["cwd"], "build.log"), 'w') as fid:
sp_config['stdout'] = fid
p = subprocess.run(cmd['Bbuild'], **sp_config)
Copy link
Collaborator

Choose a reason for hiding this comment

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

You may wrap these two build steps to catch errors, and add a mention to the log in the build.log file. Something like:

        with open(os.path.join(sp_config["cwd"], "build.log"), 'w') as fid:
            sp_config['stdout'] = fid
            try:
                p = subprocess.run(cmd['Bbuild'], **sp_config)
                p = subprocess.run(cmd['build'], **sp_config)
            except subprocess.CalledProcessError as err:
                print('Error while building simulation executable. See %s for details' % fid.name)
                print(p.stdout)
                print(p.stderr)
                raise subprocess.CalledProcessError(err)

@FabricioS
Copy link
Collaborator

Regarding this comment:

it needs to be tested on Windows. It seems that the option shell has a different behavior. It might be necessary to add a condition like:

if sys.platform.startswith('win'):
sp_config["shell"] = True
else:
sp_config["shell"] = False

Can you point where the difference is explained in the subprocess docs ? I only see a discussion about shell here : https://docs.python.org/3.5/library/subprocess.html#frequently-used-arguments.
Your code seems to come from this SO question but there is no real explanation.

Some points of interest:

  • there is no security hazard here as we do not execute user-defined commands, only secure hard-coded ones.
  • the cmake commands are the same for posix and windows (according to CMake docs)
  • shell=True allows to pass a single string for the full command line to run, which is not mandatory but convenient
  • as far as I understand, shell=True would also consider user's environment variables which may be handy.

@FabricioS
Copy link
Collaborator

Regarding

I was wondering if it is useful to print the build log file at the end of the execution? Or setting it as an option?

We may print the build.log for high values of the verbose variables (set in config.py). In relation with this question and the verbose variable, the logging module would also be useful.

Also, what do you think about removing or renaming the old simulation exec file before recompiling? It could help avoiding using an old cpp implementation.

If build errors are now catched correctly, I don't see the point of removing old executables.

@WetzelVictor
Copy link
Collaborator Author

Hi there,

Regarding old changes from PR #100

I've cleaned this PR from any other modifications. The only file concerned now is the simulation.py file

About the Shell boolean option

The SO post is the only one where it seems to be an issue. But this option seems really useful for further developments. Until further tests, lets leave it that way.

Print the build log

I've set the threshold verbose value to 3, but I don't have a clear idea of the value it should have.

Multiple commits

Sorry for the multiple commits. I had a few issues with my text editor, and the fact that I hard reseted my master branch which left some unresolved conflicts.

About this PR

On my computer, with the particular example shown in the first post of this PR, all errors are tracked down :

  • Cmake calls
  • Binary execution
  • File path errors

The code seems a little bit long, but it is more because of the formatting than actual complexity.

As always, if you have any insights on this PR, feel free to review!

Best regards,

@FabricioS
Copy link
Collaborator

In fact it is a lot of commits for a final pretty simple change.
As an exercice for your git skills, you could also squash all the commits into a single one which you pull in a new branch. This would make the tree cleaner with a single commit for a simple change.

@afalaize afalaize changed the base branch from master to develop March 4, 2018 12:40
@afalaize afalaize merged commit 50def5d into pyphs:develop Mar 4, 2018
afalaize added a commit that referenced this pull request Mar 4, 2018
Merge PR #102 Python handling Bash/CMake compilation errors
@WetzelVictor WetzelVictor deleted the bash_error_handle branch November 20, 2019 15:39
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
Labels
None yet
Projects
None yet
Development

Successfully merging this pull request may close these issues.

None yet

4 participants