Nan Zhang | ced2751 | 2017-06-19 18:01:34 -0700 | [diff] [blame] | 1 | // Copyright 2017 Google Inc. All rights reserved. |
| 2 | // |
| 3 | // Licensed under the Apache License, Version 2.0 (the "License"); |
| 4 | // you may not use this file except in compliance with the License. |
| 5 | // You may obtain a copy of the License at |
| 6 | // |
| 7 | // http://www.apache.org/licenses/LICENSE-2.0 |
| 8 | // |
| 9 | // Unless required by applicable law or agreed to in writing, software |
| 10 | // distributed under the License is distributed on an "AS IS" BASIS, |
| 11 | // WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. |
| 12 | // See the License for the specific language governing permissions and |
| 13 | // limitations under the License. |
| 14 | |
| 15 | #include "launcher_internal.h" |
| 16 | |
| 17 | #include <limits.h> |
| 18 | #include <stdio.h> |
| 19 | #include <stdlib.h> |
| 20 | |
| 21 | extern "C" { |
| 22 | // Cpython built-in C functions. |
| 23 | /* |
| 24 | read_directory(archive) -> files dict (new reference) |
| 25 | |
| 26 | Given a path to a Zip archive, build a dict, mapping file names |
| 27 | (local to the archive, using SEP as a separator) to toc entries. |
| 28 | */ |
| 29 | PyObject *read_directory(const char *archive); |
| 30 | |
| 31 | /* Given a path to a Zip file and a toc_entry, return the (uncompressed) |
| 32 | data as a new reference. */ |
| 33 | PyObject *get_data(const char *archive, PyObject *toc_entry); |
| 34 | } |
| 35 | |
| 36 | namespace android { |
| 37 | namespace cpython2 { |
| 38 | namespace python_launcher { |
Nan Zhang | 33bb4bb | 2017-12-01 20:00:54 +0000 | [diff] [blame] | 39 | namespace internal { |
Nan Zhang | ced2751 | 2017-06-19 18:01:34 -0700 | [diff] [blame] | 40 | |
| 41 | int RunModule(const char *module, int set_argv0) { |
| 42 | PyObject *runpy, *runmodule, *runargs, *result; |
| 43 | runpy = PyImport_ImportModule("runpy"); |
| 44 | if (runpy == NULL) { |
| 45 | fprintf(stderr, "Could not import runpy module\n"); |
| 46 | return -1; |
| 47 | } |
| 48 | runmodule = PyObject_GetAttrString(runpy, "_run_module_as_main"); |
| 49 | if (runmodule == NULL) { |
| 50 | fprintf(stderr, "Could not access runpy._run_module_as_main\n"); |
| 51 | Py_DECREF(runpy); |
| 52 | return -1; |
| 53 | } |
| 54 | runargs = Py_BuildValue("(si)", module, set_argv0); |
| 55 | if (runargs == NULL) { |
| 56 | fprintf(stderr, |
| 57 | "Could not create arguments for runpy._run_module_as_main\n"); |
| 58 | Py_DECREF(runpy); |
| 59 | Py_DECREF(runmodule); |
| 60 | return -1; |
| 61 | } |
| 62 | result = PyObject_Call(runmodule, runargs, NULL); |
| 63 | if (result == NULL) { |
| 64 | PyErr_Print(); |
| 65 | } |
| 66 | Py_DECREF(runpy); |
| 67 | Py_DECREF(runmodule); |
| 68 | Py_DECREF(runargs); |
| 69 | if (result == NULL) { |
| 70 | return -1; |
| 71 | } |
| 72 | Py_DECREF(result); |
| 73 | return 0; |
| 74 | } |
| 75 | |
| 76 | std::string GetEntryPointFilePath(const char *launcher_path) { |
| 77 | PyObject *files; |
| 78 | files = read_directory(launcher_path); |
| 79 | if (files == NULL) { |
| 80 | return std::string(); |
| 81 | } |
| 82 | PyObject *toc_entry; |
| 83 | // Return value: Borrowed reference. |
| 84 | toc_entry = PyDict_GetItemString(files, ENTRYPOINT_FILE); |
| 85 | if (toc_entry == NULL) { |
| 86 | Py_DECREF(files); |
| 87 | return std::string(); |
| 88 | } |
| 89 | PyObject *py_data; |
| 90 | py_data = get_data(launcher_path, toc_entry); |
| 91 | if (py_data == NULL) { |
| 92 | Py_DECREF(files); |
| 93 | return std::string(); |
| 94 | } |
| 95 | // PyString_AsString returns a NUL-terminated representation of the "py_data", |
| 96 | // "data" must not be modified in any way. And it must not be deallocated. |
| 97 | char *data = PyString_AsString(py_data); |
| 98 | if (data == NULL) { |
| 99 | Py_DECREF(py_data); |
| 100 | Py_DECREF(files); |
| 101 | return std::string(); |
| 102 | } |
| 103 | |
| 104 | char *res = strdup(data); /* deep copy of data */ |
| 105 | Py_DECREF(py_data); |
| 106 | Py_DECREF(files); |
| 107 | |
| 108 | int i = 0; |
| 109 | /* Strip newline and other trailing whitespace. */ |
| 110 | for (i = strlen(res) - 1; i >= 0 && isspace(res[i]); i--) { |
| 111 | res[i] = '\0'; |
| 112 | } |
| 113 | /* Check for the file extension. */ |
| 114 | i = strlen(res); |
| 115 | if (i > 3 && strcmp(res + i - 3, ".py") == 0) { |
| 116 | res[i - 3] = '\0'; |
| 117 | } else { |
| 118 | PyErr_Format(PyExc_ValueError, "Invalid entrypoint in %s: %s", |
| 119 | ENTRYPOINT_FILE, res); |
| 120 | return std::string(); |
| 121 | } |
| 122 | return std::string(res); |
| 123 | } |
| 124 | |
| 125 | int RunModuleNameFromEntryPoint(const char *launcher_path, std::string entrypoint) { |
| 126 | if (entrypoint.empty()) { |
| 127 | return -1; |
| 128 | } |
| 129 | // Has to pass to free to avoid a memory leak after use. |
| 130 | char *arr = strdup(entrypoint.c_str()); |
| 131 | // Replace file system path seperator with Python package/module seperator. |
| 132 | char *ch; |
| 133 | for (ch = arr; *ch; ch++) { |
| 134 | if (*ch == '/') { |
| 135 | *ch = '.'; |
| 136 | } |
| 137 | } |
| 138 | |
| 139 | if (AddPathToPythonSysPath(launcher_path) < 0) { |
| 140 | free(arr); |
| 141 | return -1; |
| 142 | } |
| 143 | // Calculate the runfiles path size. Extra space for '\0'. |
| 144 | size_t size = snprintf(nullptr, 0, "%s/%s", launcher_path, RUNFILES) + 1; |
| 145 | char runfiles_path[size]; |
| 146 | snprintf(runfiles_path, size, "%s/%s", launcher_path, RUNFILES); |
| 147 | if (AddPathToPythonSysPath(runfiles_path) < 0) { |
| 148 | free(arr); |
| 149 | return -1; |
| 150 | } |
| 151 | int ret = RunModule(arr, 0); |
| 152 | free(arr); |
| 153 | return ret; |
| 154 | } |
| 155 | |
| 156 | int AddPathToPythonSysPath(const char *path) { |
| 157 | if (path == NULL) { |
| 158 | return -1; |
| 159 | } |
| 160 | PyObject *py_path; |
| 161 | py_path = PyString_FromString(path); |
| 162 | if (py_path == NULL) { |
| 163 | return -1; |
| 164 | } |
| 165 | PyObject *sys_path; |
| 166 | // Return value: Borrowed reference. |
| 167 | sys_path = PySys_GetObject(const_cast<char*>("path")); |
| 168 | if (sys_path == NULL) { |
| 169 | Py_DECREF(py_path); |
| 170 | return -1; |
| 171 | } |
| 172 | PyList_Insert(sys_path, 0, py_path); |
| 173 | Py_DECREF(py_path); |
| 174 | return 0; |
| 175 | } |
| 176 | |
| 177 | int RunMainFromImporter(const char *launcher_path) { |
| 178 | PyObject *py_launcher_path, *importer; |
| 179 | py_launcher_path = PyString_FromString(launcher_path); |
| 180 | if (py_launcher_path == NULL) { |
| 181 | return -1; |
| 182 | } |
| 183 | importer = PyImport_GetImporter(py_launcher_path); |
| 184 | if (importer == NULL) { |
| 185 | Py_DECREF(py_launcher_path); |
| 186 | return -1; |
| 187 | } |
| 188 | if (importer != Py_None && importer->ob_type != &PyNullImporter_Type) { |
| 189 | /* Launcher path is usable as an import source, so |
| 190 | put it in sys.path[0] and import __main__ */ |
| 191 | if (AddPathToPythonSysPath(launcher_path) < 0) { |
| 192 | Py_DECREF(importer); |
| 193 | Py_DECREF(py_launcher_path); |
| 194 | return -1; |
| 195 | } |
| 196 | } |
| 197 | Py_DECREF(importer); |
| 198 | Py_DECREF(py_launcher_path); |
| 199 | return RunModule("__main__", 0); |
| 200 | } |
| 201 | } // namespace internal |
| 202 | |
| 203 | int RunEntryPointOrMainModule(const char *launcher_path) { |
| 204 | std::string entrypoint = internal::GetEntryPointFilePath(launcher_path); |
| 205 | if (entrypoint.empty()) { |
| 206 | // If entry point can not be found or can not be executed, we try to |
| 207 | // run __main__.py within the .par file. |
| 208 | fprintf(stderr, "Cannot find valid entry point to execute par file!\n"); |
| 209 | fprintf(stdout, "Start trying to run __main__ module within par file.\n"); |
| 210 | return internal::RunMainFromImporter(launcher_path); |
| 211 | } |
| 212 | return internal::RunModuleNameFromEntryPoint(launcher_path, entrypoint); |
| 213 | } |
| 214 | } // namespace python_launcher |
| 215 | } // namespace cpython2 |
| 216 | } // namespace android |