// Copyright 2017 Google Inc. All rights reserved.
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
#include "launcher_internal.h"
#include <limits.h>
#include <stdio.h>
#include <stdlib.h>
extern "C" {
// Cpython built-in C functions.
/*
read_directory(archive) -> files dict (new reference)
Given a path to a Zip archive, build a dict, mapping file names
(local to the archive, using SEP as a separator) to toc entries.
*/
PyObject *read_directory(const char *archive);
/* Given a path to a Zip file and a toc_entry, return the (uncompressed)
data as a new reference. */
PyObject *get_data(const char *archive, PyObject *toc_entry);
}
namespace android {
namespace cpython2 {
namespace python_launcher {
namespace internal {
int RunModule(const char *module, int set_argv0) {
PyObject *runpy, *runmodule, *runargs, *result;
runpy = PyImport_ImportModule("runpy");
if (runpy == NULL) {
fprintf(stderr, "Could not import runpy module\n");
return -1;
}
runmodule = PyObject_GetAttrString(runpy, "_run_module_as_main");
if (runmodule == NULL) {
fprintf(stderr, "Could not access runpy._run_module_as_main\n");
Py_DECREF(runpy);
return -1;
}
runargs = Py_BuildValue("(si)", module, set_argv0);
if (runargs == NULL) {
fprintf(stderr,
"Could not create arguments for runpy._run_module_as_main\n");
Py_DECREF(runpy);
Py_DECREF(runmodule);
return -1;
}
result = PyObject_Call(runmodule, runargs, NULL);
if (result == NULL) {
PyErr_Print();
}
Py_DECREF(runpy);
Py_DECREF(runmodule);
Py_DECREF(runargs);
if (result == NULL) {
return -1;
}
Py_DECREF(result);
return 0;
}
std::string GetEntryPointFilePath(const char *launcher_path) {
PyObject *files;
files = read_directory(launcher_path);
if (files == NULL) {
return std::string();
}
PyObject *toc_entry;
// Return value: Borrowed reference.
toc_entry = PyDict_GetItemString(files, ENTRYPOINT_FILE);
if (toc_entry == NULL) {
Py_DECREF(files);
return std::string();
}
PyObject *py_data;
py_data = get_data(launcher_path, toc_entry);
if (py_data == NULL) {
Py_DECREF(files);
return std::string();
}
// PyString_AsString returns a NUL-terminated representation of the "py_data",
// "data" must not be modified in any way. And it must not be deallocated.
char *data = PyString_AsString(py_data);
if (data == NULL) {
Py_DECREF(py_data);
Py_DECREF(files);
return std::string();
}
char *res = strdup(data); /* deep copy of data */
Py_DECREF(py_data);
Py_DECREF(files);
int i = 0;
/* Strip newline and other trailing whitespace. */
for (i = strlen(res) - 1; i >= 0 && isspace(res[i]); i--) {
res[i] = '\0';
}
/* Check for the file extension. */
i = strlen(res);
if (i > 3 && strcmp(res + i - 3, ".py") == 0) {
res[i - 3] = '\0';
} else {
PyErr_Format(PyExc_ValueError, "Invalid entrypoint in %s: %s",
ENTRYPOINT_FILE, res);
return std::string();
}
return std::string(res);
}
int RunModuleNameFromEntryPoint(const char *launcher_path, std::string entrypoint) {
if (entrypoint.empty()) {
return -1;
}
// Has to pass to free to avoid a memory leak after use.
char *arr = strdup(entrypoint.c_str());
// Replace file system path seperator with Python package/module seperator.
char *ch;
for (ch = arr; *ch; ch++) {
if (*ch == '/') {
*ch = '.';
}
}
if (AddPathToPythonSysPath(launcher_path) < 0) {
free(arr);
return -1;
}
// Calculate the runfiles path size. Extra space for '\0'.
size_t size = snprintf(nullptr, 0, "%s/%s", launcher_path, RUNFILES) + 1;
char runfiles_path[size];
snprintf(runfiles_path, size, "%s/%s", launcher_path, RUNFILES);
if (AddPathToPythonSysPath(runfiles_path) < 0) {
free(arr);
return -1;
}
int ret = RunModule(arr, 0);
free(arr);
return ret;
}
int AddPathToPythonSysPath(const char *path) {
if (path == NULL) {
return -1;
}
PyObject *py_path;
py_path = PyString_FromString(path);
if (py_path == NULL) {
return -1;
}
PyObject *sys_path;
// Return value: Borrowed reference.
sys_path = PySys_GetObject(const_cast<char*>("path"));
if (sys_path == NULL) {
Py_DECREF(py_path);
return -1;
}
PyList_Insert(sys_path, 0, py_path);
Py_DECREF(py_path);
return 0;
}
int RunMainFromImporter(const char *launcher_path) {
PyObject *py_launcher_path, *importer;
py_launcher_path = PyString_FromString(launcher_path);
if (py_launcher_path == NULL) {
return -1;
}
importer = PyImport_GetImporter(py_launcher_path);
if (importer == NULL) {
Py_DECREF(py_launcher_path);
return -1;
}
if (importer != Py_None && importer->ob_type != &PyNullImporter_Type) {
/* Launcher path is usable as an import source, so
put it in sys.path[0] and import __main__ */
if (AddPathToPythonSysPath(launcher_path) < 0) {
Py_DECREF(importer);
Py_DECREF(py_launcher_path);
return -1;
}
}
Py_DECREF(importer);
Py_DECREF(py_launcher_path);
return RunModule("__main__", 0);
}
} // namespace internal
int RunEntryPointOrMainModule(const char *launcher_path) {
std::string entrypoint = internal::GetEntryPointFilePath(launcher_path);
if (entrypoint.empty()) {
// If entry point can not be found or can not be executed, we try to
// run __main__.py within the .par file.
fprintf(stderr, "Cannot find valid entry point to execute par file!\n");
fprintf(stdout, "Start trying to run __main__ module within par file.\n");
return internal::RunMainFromImporter(launcher_path);
}
return internal::RunModuleNameFromEntryPoint(launcher_path, entrypoint);
}
} // namespace python_launcher
} // namespace cpython2
} // namespace android