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

add win_ros script wrappers to make python scripts executable #978

Merged
merged 28 commits into from
Mar 4, 2019
Merged
Show file tree
Hide file tree
Changes from 16 commits
Commits
Show all changes
28 commits
Select commit Hold shift + click to select a range
dbc776f
Move win_ros templates into catkin to broadly enable script wrappers.
Aug 15, 2018
7635cf4
Return python process exit code from wrapper (#7)
johnsonshih Aug 28, 2018
13b7b83
clean up format for ros_bin.cpp (#30)
kejxu Jan 8, 2019
aefcfba
Merge branch 'kinetic-devel' into add_win_ros_script_wrappers
kejxu Jan 29, 2019
4197c17
add docblock for add_python_executable_helper, remove batch helper, m…
kejxu Feb 12, 2019
cfd4750
use oneValueArgs to parse CMake function arguments
kejxu Feb 12, 2019
f924805
remove unused batch wrapper template
kejxu Feb 12, 2019
bd7fa45
correct name in BSD license
kejxu Feb 23, 2019
bb827fd
windows.h should always be added on Windows, change to use _WIN32
kejxu Feb 23, 2019
9a546ec
create separate main with syntax error if not on Windows
kejxu Feb 23, 2019
c39fd22
remove comments and make all implementation Windows-specifc
kejxu Feb 23, 2019
127eaa0
refactor CreateProcess, add :: for global namespace
kejxu Feb 23, 2019
8d31aeb
refactor command preparation section
kejxu Feb 23, 2019
69110f1
add debug section for command preparation
kejxu Feb 23, 2019
6cf1add
rename ros_bin.cpp to python_win32_wrapper.cpp since it is not ros-sp…
kejxu Feb 23, 2019
5d4c7c1
revert unicode-related changes
kejxu Feb 23, 2019
8aec676
intergrate add_wrapper command into catkin_install_python and catkin_…
kejxu Feb 24, 2019
8c51edb
remove if (NOT TARGET) mask, rename to add_python_executable, print e…
kejxu Feb 27, 2019
75a4829
update comment for FindPythonScript, use MAX_PATH for GetModuleFileNa…
kejxu Feb 27, 2019
9ce8aa3
update wrapper.cpp to .cpp.in and configure in cmake
kejxu Feb 27, 2019
13897e9
remove DEBUG flag, merge style
kejxu Feb 27, 2019
1810908
cleanup add_python_executable function
dirk-thomas Feb 27, 2019
86949ef
update setup.bat to call _setup_util.py.exe if it exists
kejxu Feb 27, 2019
97ea424
update comment
kejxu Feb 27, 2019
d5024c8
avoid duplicating definition of variables, reuse _SETUP_UTIl
kejxu Feb 27, 2019
0385a72
revert change to setup.bat.in, always call _setup_util.py with Python…
kejxu Feb 28, 2019
dd22d7e
explicitly enable C++ for wrapper (#41)
kejxu Mar 1, 2019
14ade90
correct the location to enable c++ (#42)
kejxu Mar 1, 2019
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
13 changes: 13 additions & 0 deletions CMakeLists.txt
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,19 @@ install(DIRECTORY cmake

catkin_python_setup()

set(PYTHON_SCRIPTS
catkin_find
catkin_init_workspace
catkin_make
catkin_make_isolated
catkin_test_results
catkin_topological_order)
foreach(script ${PYTHON_SCRIPTS})
add_python_executable_helper(
SCRIPT_NAME ${script}
TARGET_NAME ${script}_executable)
endforeach()

if(CATKIN_ENABLE_TESTING)
catkin_add_nosetests(test/local_tests)
catkin_add_nosetests(test/unit_tests)
Expand Down
36 changes: 36 additions & 0 deletions cmake/platform/windows.cmake
Original file line number Diff line number Diff line change
Expand Up @@ -88,3 +88,39 @@ if(MSVC)
# https://blogs.msdn.microsoft.com/vcblog/2018/04/09/msvc-now-correctly-reports-__cplusplus/
add_compile_options(/Zc:__cplusplus)
endif()

#
# Helper function to add Python executable wrapper on Windows due to lack of shebang support
#
# Code courtesy of Yujin Robot. Derived from https://github.com/ros-windows/win_ros
#

#
# Add Python executable wrapper around Python scripts on Windows.
# Python scripts with (or without) .py extension are not executable on Windows due to lack of shebang support
#
# :param script: Python script name that needs a wrapper
dirk-thomas marked this conversation as resolved.
Show resolved Hide resolved
# :type script: string
# :param target: build target name
dirk-thomas marked this conversation as resolved.
Show resolved Hide resolved
# :type target: string
dirk-thomas marked this conversation as resolved.
Show resolved Hide resolved
#
# @public
#
function(add_python_executable_helper)
dirk-thomas marked this conversation as resolved.
Show resolved Hide resolved
set(options)
set(oneValueArgs SCRIPT_NAME TARGET_NAME)
set(multiValueArgs)
cmake_parse_arguments(add_python_executable_helper "${options}" "${oneValueArgs}" "${multiValueArgs}" ${ARGN})

if (WIN32)
add_executable(${add_python_executable_helper_TARGET_NAME} ${catkin_EXTRAS_DIR}/templates/python_win32_wrapper.cpp)

# The actual file name of the executable built on Windows will be ${add_python_executable_helper_SCRIPT_NAME}.exe
set_target_properties(${add_python_executable_helper_TARGET_NAME} PROPERTIES
OUTPUT_NAME ${add_python_executable_helper_SCRIPT_NAME}
RUNTIME_OUTPUT_DIRECTORY ${CATKIN_DEVEL_PREFIX}/${CATKIN_GLOBAL_BIN_DESTINATION})

install(TARGETS ${add_python_executable_helper_TARGET_NAME}
RUNTIME DESTINATION ${CATKIN_GLOBAL_BIN_DESTINATION})
endif()
endfunction()
177 changes: 177 additions & 0 deletions cmake/templates/python_win32_wrapper.cpp
Original file line number Diff line number Diff line change
@@ -0,0 +1,177 @@
//
// Software License Agreement (BSD License)
//
// Copyright (c) 2011, Yujin Robot
// All rights reserved.
//
// Redistribution and use in source and binary forms, with or without
// modification, are permitted provided that the following conditions
// are met:
//
// * Redistributions of source code must retain the above copyright
// notice, this list of conditions and the following disclaimer.
// * Redistributions in binary form must reproduce the above
// copyright notice, this list of conditions and the following
// disclaimer in the documentation and/or other materials provided
// with the distribution.
// * Neither the name of Yujin Robot nor the names of its
// contributors may be used to endorse or promote products derived
// from this software without specific prior written permission.
//
// THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS
// "AS IS" AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT
// LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS
// FOR A PARTICULAR PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL THE
// COPYRIGHT OWNER OR CONTRIBUTORS BE LIABLE FOR ANY DIRECT, INDIRECT,
// INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES (INCLUDING,
// BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR SERVICES;
// LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
// CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT
// LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN
// ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
// POSSIBILITY OF SUCH DAMAGE.
//

/*
* @file python_win32_wrapper.cpp
*
* @brief Launch a Python script located in the same location and have the same name as the compiled executable
*
* @date March 2011
*/

#ifdef _WIN32

#include <iostream>
#include <windows.h>

int main(int argc, char* argv[]) try
{
const auto GetCurrentModuleName = []() -> std::string
{
// initialize buffer string with at least one character
std::string moduleName = " ";
while (true)
{
// retrieves the path of the executable file of the current process
auto result = ::GetModuleFileName(nullptr, &moduleName[0], static_cast<DWORD>(moduleName.size()));
if (!result)
{
throw ::GetLastError();
}
else if (result == moduleName.size() && ::GetLastError() == ERROR_INSUFFICIENT_BUFFER)
{
// buffer not large enough
moduleName.resize(moduleName.size() * 2);
continue;
dirk-thomas marked this conversation as resolved.
Show resolved Hide resolved
}
moduleName.resize(result);
break;
}
return moduleName;
};

const auto FindPythonScript = [](const std::string& exeName) -> std::string
{
// this could become more fail-proof and readable with https://en.cppreference.com/w/cpp/filesystem from c++20
const std::string exeExtension = ".exe";
if (exeName.size() <= exeExtension.size() || exeName.substr(exeName.size() - exeExtension.size()) != exeExtension)
{
throw L"Invalid name.";
}

// use quoted string to ensure the correct path is used
return "\"" + exeName.substr(0, exeName.size() - exeExtension.size()) + "\"";
dirk-thomas marked this conversation as resolved.
Show resolved Hide resolved
};

const auto GetPythonExecutable = []() -> std::string
{
std::string pythonExecutable = "python";
dirk-thomas marked this conversation as resolved.
Show resolved Hide resolved

// use quoted string to indicate where the file name ends and the arguments begin
return "\"" + pythonExecutable + "\"";
};

const auto pythonExecutable = GetPythonExecutable();
const auto pythonScript = FindPythonScript(GetCurrentModuleName());
std::string command = pythonExecutable + " " + pythonScript;
for (auto i = 1; i < argc; ++i)
{
command += " ";
// use quoted strings to handle spaces within each argument
command += " \"" + std::string(argv[i]) + "\"";
}

#if defined(DEBUG)
for (auto i = 0; i < argc; ++i)
{
printf("[DEBUG] %d:\t%s\n", i, argv[i]);
}

printf("[DEBUG] script: %s\n", pythonScript.c_str());
printf("[DEBUG] command: %s\n", command.c_str());
#endif

STARTUPINFO startup_info;
PROCESS_INFORMATION process_info;
::memset(&startup_info, 0, sizeof(startup_info));
::memset(&process_info, 0, sizeof(process_info));
startup_info.cb = sizeof(startup_info);

auto result = ::CreateProcess(
nullptr, // program to execute (nullptr = execute command line)
&command[0], // command line to execute
nullptr, // process security attributes
nullptr, // thread security attributes
false, // determines if handles from parent process are inherited
0, // no creation flags
nullptr, // enviornment (nullptr = use parent's)
nullptr, // current directory (nullptr = use parent's)
&startup_info, // startup info
&process_info // process info
);
if (!result)
{
const auto error = ::GetLastError();
switch (error)
{
case ERROR_FILE_NOT_FOUND:
{
std::cout << "Error! Python executable cannot be found, is it added to PATH?" << std::endl;
dirk-thomas marked this conversation as resolved.
Show resolved Hide resolved
break;
}
default:
std::cout << "Error! CreateProcess failed with error code: " << error << std::endl;
break;
}
throw error;
}
::WaitForSingleObject(process_info.hProcess, INFINITE);
unsigned long exitCode = NO_ERROR;
::GetExitCodeProcess(process_info.hProcess, &exitCode);
::CloseHandle(process_info.hProcess);
::CloseHandle(process_info.hThread);

#if defined(DEBUG)
printf("[DEBUG] process exist code: %ld\n", exitCode);
#endif

return exitCode;
}
catch (...)
{
std::cout << "Failed to execute the Python script..." << std::endl;
return 1;
}

#else

#error This wrapper should only be created on Windows.

int main()
{
// deliberately add syntax error when the file is not supposed to get compiled
return 0
}

#endif