Panda3D
mayapath.cxx
1 // Filename: mayapath.cxx
2 // Created by: drose (07Apr08)
3 //
4 ////////////////////////////////////////////////////////////////////
5 //
6 // PANDA 3D SOFTWARE
7 // Copyright (c) Carnegie Mellon University. All rights reserved.
8 //
9 // All use of this software is subject to the terms of the revised BSD
10 // license. You should have received a copy of this license along
11 // with this source code in a file named "LICENSE."
12 //
13 ////////////////////////////////////////////////////////////////////
14 
15 // This program works as a stub to launch maya2egg, egg2maya, and
16 // similar programs that invoke OpenMaya and require certain
17 // environment variables to be set first.
18 
19 // It used to duplicate code in mayaWrapper.cxx, but now the
20 // functionality for these two separate programs are unified here.
21 
22 // If MAYAVERSION is defined at the time this is compiled, then that
23 // particular version of Maya is insisted upon, and the desired Maya
24 // location is found in the Registry; otherwise, we require that
25 // $MAYA_LOCATION be set at runtime and points to the desired Maya
26 // installation.
27 
28 // If MAYAVERSION is defined and $MAYA_LOCATION is also set, then we
29 // check that definition of $MAYA_LOCATION is reasonable, which we
30 // define as pointing to the same version of OpenMaya.dll. If so,
31 // then we use the runtime $MAYA_LOCATION, allowing the user to
32 // (slightly) override the runtime Maya directory. If $MAYA_LOCATION
33 // is set but points to a different version of OpenMaya.dll, we ignore
34 // it altogether and replace it with our registry data, which allows
35 // the user to have MAYA_LOCATION pointing to a different version of
36 // Maya without interfering with this program.
37 
38 #include "dtoolbase.h"
39 #include "filename.h"
40 #include "globPattern.h"
41 #include "dSearchPath.h"
42 #include "executionEnvironment.h"
43 #include "hashVal.h"
44 #include <stdlib.h>
45 
46 #if defined(_WIN32)
47 #define WIN32_LEAN_AND_MEAN
48 #include <windows.h>
49 #else
50 #include <sys/stat.h>
51 #endif
52 
53 #ifdef HAVE_PYTHON
54 #include "pystub.h"
55 #endif
56 
57 #define QUOTESTR(x) #x
58 #define TOSTRING(x) QUOTESTR(x)
59 
60 #if defined(_WIN32)
61 // Filename::dso_filename changes .so to .dll automatically.
62 static const Filename openmaya_filename = "bin/OpenMaya.so";
63 #elif defined(IS_OSX)
64 static const Filename openmaya_filename = "MacOS/libOpenMaya.dylib";
65 #else
66 static const Filename openmaya_filename = "lib/libOpenMaya.so";
67 #endif // _WIN32
68 
69 // Searches for python26.zip or whatever version it is.
70 static Filename
71 find_pyzip(const Filename &maya_location) {
72  // This is where python26.zip appears on Windows. Should it be in
73  // other locations on other platforms?
74  Filename dirname(maya_location, "bin");
75 
76  vector_string results;
77  GlobPattern glob("python*.zip");
78  if (glob.match_files(results, dirname) != 0) {
79  return Filename(dirname, results[0]);
80  }
81 
82  return Filename();
83 }
84 
85 struct MayaVerInfo {
86  const char *ver, *key;
87 };
88 
89 struct MayaVerInfo maya_versions[] = {
90  { "MAYA6", "6.0" },
91  { "MAYA65", "6.5" },
92  { "MAYA7", "7.0" },
93  { "MAYA8", "8.0" },
94  { "MAYA85", "8.5" },
95  { "MAYA2008", "2008"},
96  { "MAYA2009", "2009"},
97  { "MAYA2010", "2010"},
98  { "MAYA2011", "2011"},
99  { "MAYA2012", "2012"},
100  { "MAYA2013", "2013"},
101  { "MAYA20135", "2013.5"},
102  { "MAYA2014", "2014"},
103  { "MAYA2015", "2015"},
104  { "MAYA2016", "2016"},
105  { "MAYA20165", "2016.5"},
106  { 0, 0 },
107 };
108 
109 static const char *
110 get_version_number(const char *ver) {
111  for (int i = 0; maya_versions[i].ver != 0; ++i) {
112  if (strcmp(maya_versions[i].ver, ver) == 0) {
113  return maya_versions[i].key;
114  }
115  }
116  return 0;
117 }
118 
119 #if defined(_WIN32)
120 static void
121 get_maya_location(const char *ver, string &loc) {
122  char fullkey[1024];
123  const char *developer;
124  LONG res;
125 
126  for (int dev=0; dev<3; dev++) {
127  switch (dev) {
128  case 0: developer="Alias|Wavefront"; break;
129  case 1: developer="Alias"; break;
130  case 2: developer="Autodesk"; break;
131  }
132  sprintf(fullkey, "SOFTWARE\\%s\\Maya\\%s\\Setup\\InstallPath", developer, ver);
133  for (int hive=0; hive<2; hive++) {
134  HKEY hkey;
135  res = RegOpenKeyEx(HKEY_LOCAL_MACHINE, fullkey, 0, KEY_READ | (hive ? 256:0), &hkey);
136  if (res == ERROR_SUCCESS) {
137  DWORD dtype;
138  DWORD size = 4096;
139  char result[4096 + 1];
140  res = RegQueryValueEx(hkey, "MAYA_INSTALL_LOCATION", NULL, &dtype, (LPBYTE)result, &size);
141  if ((res == ERROR_SUCCESS)&&(dtype == REG_SZ)) {
142  result[size] = 0;
143  loc = result;
144  }
145  RegCloseKey(hkey);
146  }
147  }
148  }
149 }
150 
151 #elif defined(__APPLE__)
152 static void
153 get_maya_location(const char *ver, string &loc) {
154  char mpath[64];
155  sprintf(mpath, "/Applications/Autodesk/maya%s/Maya.app/Contents", ver);
156  struct stat st;
157  if(stat(mpath, &st) == 0) {
158  loc = mpath;
159  }
160 }
161 
162 #else // _WIN32
163 static void
164 get_maya_location(const char *ver, string &loc) {
165  char mpath[64];
166 #if __WORDSIZE == 64
167  sprintf(mpath, "/usr/autodesk/maya%s-x64", ver);
168 #else
169  sprintf(mpath, "/usr/autodesk/maya%s", ver);
170 #endif
171  struct stat st;
172  if(stat(mpath, &st) == 0) {
173  loc = mpath;
174  } else {
175 #if __WORDSIZE == 64
176  sprintf(mpath, "/usr/aw/maya%s-x64", ver);
177 #else
178  sprintf(mpath, "/usr/aw/maya%s", ver);
179 #endif
180  if(stat(mpath, &st) == 0) {
181  loc = mpath;
182  }
183  }
184 }
185 
186 #endif // _WIN32
187 
188 
189 int
190 main(int argc, char *argv[]) {
191 #ifdef HAVE_PYTHON
192  // Force pystub to be linked in.
193  pystub();
194 #endif
195 
196  // First, get the command line and append _bin, so we will actually
197  // run maya2egg_bin.exe, egg2maya_bin.exe, etc.
198  Filename command = Filename::from_os_specific(argv[0]);
199  if (!command.is_fully_qualified()) {
200  DSearchPath path;
202 #ifdef _WIN32
203  command.set_extension("exe");
204 #endif
205  command.resolve_filename(path);
206  }
207 
208 #ifdef _WIN32
209  if (command.get_extension() == "exe") {
210  command.set_extension("");
211  }
212 #endif
213 
214  command = command.get_fullpath() + string("_bin");
215 #ifdef _WIN32
216  command.set_extension("exe");
217 #endif
218  string os_command = command.to_os_specific();
219 
220  // First start with $PANDA_MAYA_LOCATION. If it is set, it
221  // overrides everything else.
222  Filename maya_location = Filename::expand_from("$PANDA_MAYA_LOCATION");
223  if (!maya_location.empty()) {
224  // Reset maya_location to its full long name, because Maya
225  // requires this.
226  maya_location.make_canonical();
227  maya_location = Filename::from_os_specific(maya_location.to_os_long_name());
228 
229  } else {
230  // $PANDA_MAYA_LOCATION wasn't set, so check the normal locations.
231  // First, we get the standard location, as a point of reference.
232  Filename standard_maya_location;
233 #ifdef MAYAVERSION
234  const char *key = get_version_number(TOSTRING(MAYAVERSION));
235  if (key == NULL) {
236  cerr << "Unknown Maya version: " << TOSTRING(MAYAVERSION) << "\n";
237  } else {
238  string loc;
239  get_maya_location(key, loc);
240  if (loc.empty()) {
241  cerr << "Cannot locate " << TOSTRING(MAYAVERSION) << ": it does not appear to be installed.\n";
242  } else {
243  standard_maya_location = Filename::from_os_specific(loc);
244  }
245  }
246  if (!standard_maya_location.empty()) {
247  // Reset standard_maya_location to its full long name, so we can
248  // compare reliably to the given version.
249  standard_maya_location.make_canonical();
250  standard_maya_location = Filename::from_os_specific(standard_maya_location.to_os_long_name());
251  }
252 #endif // MAYAVERSION
253 
254  // Now check if $MAYA_LOCATION is set. If it is, and it's
255  // consistent with the standard location, we respect it.
256  maya_location = Filename::expand_from("$MAYA_LOCATION");
257  if (!maya_location.empty()) {
258  // Reset maya_location to its full long name, so we can compare
259  // it reliably to the standard location; and also because Maya
260  // requires this.
261  maya_location.make_canonical();
262  maya_location = Filename::from_os_specific(maya_location.to_os_long_name());
263  }
264 
265  if (maya_location.empty()) {
266  // If it is not set, we use the standard version instead.
267  maya_location = standard_maya_location;
268 
269  } else if (maya_location != standard_maya_location) {
270  // If it *is* set, we verify that OpenMaya.dll matches the
271  // standard version.
272  Filename openmaya_given = Filename::dso_filename(Filename(maya_location, openmaya_filename));
273  Filename openmaya_standard = Filename::dso_filename(Filename(standard_maya_location, openmaya_filename));
274 
275  if (openmaya_given != openmaya_standard) {
276 #ifdef HAVE_OPENSSL
277  // If we have OpenSSL, we can use it to check the md5 hashes of
278  // the DLL.
279  HashVal hash_given, hash_standard;
280  if (!hash_standard.hash_file(openmaya_standard)) {
281  // Couldn't read the standard file, so use the given one.
282 
283  } else {
284  if (!hash_given.hash_file(openmaya_given)) {
285  // Couldn't even read the given file; use the standard one
286  // instead.
287  maya_location = standard_maya_location;
288 
289  } else {
290  if (hash_standard != hash_given) {
291  // No match; it must be the wrong version.
292  cerr << "$MAYA_LOCATION points to wrong version; using standard location instead.\n";
293  maya_location = standard_maya_location;
294  } else {
295  // The hash matches; keep the given MAYA_LOCATION setting.
296  }
297  }
298  }
299 #else // HAVE_OPENSSL
300  // Without OpenSSL, just check the DLL filesize only.
301  off_t size_given, size_standard;
302  size_standard = openmaya_standard.get_file_size();
303  if (size_standard == 0) {
304  // Couldn't read the standard file, so use the given one.
305 
306  } else {
307  size_given = openmaya_given.get_file_size();
308  if (size_given == 0) {
309  // Couldn't even read the given file; use the standard one
310  // instead.
311  maya_location = standard_maya_location;
312 
313  } else {
314  if (size_standard != size_given) {
315  // No match; it must be the wrong version.
316  cerr << "$MAYA_LOCATION points to wrong version; using standard location instead.\n";
317  maya_location = standard_maya_location;
318 
319  } else {
320  // The size matches; keep the given MAYA_LOCATION setting.
321  }
322  }
323  }
324 
325 #endif // HAVE_OPENSSL
326  }
327  }
328  }
329 
330  if (maya_location.empty()) {
331  cerr << "$MAYA_LOCATION is not set!\n";
332  exit(1);
333  }
334 
335  cerr << "MAYA_LOCATION: " << maya_location.to_os_specific() << endl;
336  if (!maya_location.is_directory()) {
337  cerr << "The directory referred to by $MAYA_LOCATION does not exist!\n";
338  exit(1);
339  }
340 
341  // Look for OpenMaya.dll as a sanity check.
342  Filename openmaya = Filename::dso_filename(Filename(maya_location, openmaya_filename));
343  if (!openmaya.is_regular_file()) {
344  cerr << "Could not find $MAYA_LOCATION/" << Filename::dso_filename(openmaya_filename).to_os_specific() << "!\n";
345  exit(1);
346  }
347 
348  // Re-set MAYA_LOCATION to its properly sanitized form.
349  {
350  string putenv_str = "MAYA_LOCATION=" + maya_location.to_os_specific();
351  char *putenv_cstr = strdup(putenv_str.c_str());
352  putenv(putenv_cstr);
353  }
354 
355 #ifdef WIN32
356  string sep = ";";
357 #else
358  string sep = ":";
359 #endif
360 
361  // Now set PYTHONHOME & PYTHONPATH. Maya2008 requires this to be
362  // set and pointing within $MAYA_LOCATION, or it might get itself
363  // confused with another Python installation (e.g. Panda's).
364  Filename python = Filename(maya_location, "Python");
365  if (python.is_directory()) {
366  {
367  string putenv_str = "PYTHONHOME=" + python.to_os_specific();
368  char *putenv_cstr = strdup(putenv_str.c_str());
369  putenv(putenv_cstr);
370  }
371  {
372  string putenv_str = "PYTHONPATH=" + python.to_os_specific();
373 
374  Filename pyzip = find_pyzip(maya_location);
375  if (!pyzip.empty() && pyzip.exists()) {
376  putenv_str += sep;
377  putenv_str += pyzip.to_os_specific();
378  }
379 
380  Filename site_packages(python, "lib/site-packages");
381  if (site_packages.is_directory()) {
382  putenv_str += sep;
383  putenv_str += site_packages.to_os_specific();
384  }
385 
386  char *putenv_cstr = strdup(putenv_str.c_str());
387  putenv(putenv_cstr);
388  }
389  }
390 
391  // Also put the Maya bin directory on the PATH.
392 #ifdef IS_OSX
393  Filename bin = Filename(maya_location, "MacOS");
394 #else
395  Filename bin = Filename(maya_location, "bin");
396 #endif
397  if (bin.is_directory()) {
398  const char *path = getenv("PATH");
399  if (path == NULL) {
400  path = "";
401  }
402  string putenv_str = "PATH=" + bin.to_os_specific() + sep + path;
403  char *putenv_cstr = strdup(putenv_str.c_str());
404  putenv(putenv_cstr);
405  }
406 
407 #ifdef IS_OSX
408  // And on DYLD_LIBRARY_PATH.
409  if (bin.is_directory()) {
410  const char *path = getenv("DYLD_LIBRARY_PATH");
411  if (path == NULL) {
412  path = "";
413  }
414  string sep = ":";
415  string putenv_str = "DYLD_LIBRARY_PATH=" + bin.to_os_specific() + sep + path;
416  char *putenv_cstr = strdup(putenv_str.c_str());
417  putenv(putenv_cstr);
418  }
419 
420  // We also have to give it a way to find the Maya frameworks.
421  Filename fw_dir = Filename(maya_location, "Frameworks");
422  if (fw_dir.is_directory()) {
423  const char *path = getenv("DYLD_FALLBACK_FRAMEWORK_PATH");
424  if (path == NULL) {
425  path = "";
426  }
427  string sep = ":";
428  string putenv_str = "DYLD_FALLBACK_FRAMEWORK_PATH=" + fw_dir.to_os_specific() + sep + path;
429  char *putenv_cstr = strdup(putenv_str.c_str());
430  putenv(putenv_cstr);
431  }
432 
433 #elif !defined(_WIN32)
434  // Linux (or other non-Windows OS) gets it added to LD_LIBRARY_PATH.
435  if (bin.is_directory()) {
436  const char *path = getenv("LD_LIBRARY_PATH");
437  if (path == NULL) {
438  path = "";
439  }
440  string sep = ":";
441  string putenv_str = "LD_LIBRARY_PATH=" + bin.to_os_specific() + sep + path;
442  char *putenv_cstr = strdup(putenv_str.c_str());
443  putenv(putenv_cstr);
444  }
445 
446 #endif // IS_OSX
447 
448  // When this is set, Panda3D will try not to use any functions from the
449  // CPython API. This is necessary because Maya links with its own copy
450  // of Python, which may be incompatible with ours.
451  putenv((char *)"PANDA_INCOMPATIBLE_PYTHON=1");
452 
453  // Now that we have set up the environment variables properly, chain
454  // to the actual maya2egg_bin (or whichever) executable.
455 
456 #ifdef _WIN32
457  // Windows case.
458  char *command_line = strdup(GetCommandLine());
459  STARTUPINFO startup_info;
460  PROCESS_INFORMATION process_info;
461  GetStartupInfo(&startup_info);
462  BOOL result = CreateProcess(os_command.c_str(),
463  command_line,
464  NULL, NULL, true, 0,
465  NULL, NULL,
466  &startup_info,
467  &process_info);
468  if (result) {
469  WaitForSingleObject(process_info.hProcess, INFINITE);
470  DWORD exit_code = 0;
471 
472  if (GetExitCodeProcess(process_info.hProcess, &exit_code)) {
473  if (exit_code != 0) {
474  cerr << "Program exited with status " << exit_code << "\n";
475  }
476  }
477 
478  CloseHandle(process_info.hProcess);
479  CloseHandle(process_info.hThread);
480  exit(exit_code);
481  }
482  cerr << "Couldn't execute " << command << ": " << GetLastError() << "\n";
483 
484 #else
485  // Unix case.
486  execvp(os_command.c_str(), argv);
487 #endif
488 
489  // Couldn't execute for some reason.
490  return 1;
491 }
void set_extension(const string &s)
Replaces the file extension.
Definition: filename.cxx:837
string get_fullpath() const
Returns the entire filename: directory, basename, extension.
Definition: filename.I:398
streamsize get_file_size() const
Returns the size of the file in bytes, or 0 if there is an error.
Definition: filename.cxx:1665
bool make_canonical()
Converts this filename to a canonical name by replacing the directory part with the fully-qualified d...
Definition: filename.cxx:1072
Stores a 128-bit value that represents the hashed contents (typically MD5) of a file or buffer...
Definition: hashVal.h:32
static Filename expand_from(const string &user_string, Type type=T_general)
Returns the same thing as from_os_specific(), but embedded environment variable references (e...
Definition: filename.cxx:418
bool is_fully_qualified() const
Returns true if the filename is fully qualified, e.g.
Definition: filename.I:682
string get_extension() const
Returns the file extension.
Definition: filename.I:477
void append_path(const string &path, const string &separator=string())
Adds all of the directories listed in the search path to the end of the search list.
bool resolve_filename(const DSearchPath &searchpath, const string &default_extension=string())
Searches the given search path for the filename.
Definition: filename.cxx:1699
The name of a file, such as a texture file or an Egg file.
Definition: filename.h:44
string to_os_long_name() const
This is the opposite of to_os_short_name(): it returns the "long name" of the filename, if the filename exists.
Definition: filename.cxx:1322
static string get_environment_variable(const string &var)
Returns the definition of the indicated environment variable, or the empty string if the variable is ...
bool is_regular_file() const
Returns true if the filename exists and is the name of a regular file (i.e.
Definition: filename.cxx:1389
bool is_directory() const
Returns true if the filename exists and is a directory name, false otherwise.
Definition: filename.cxx:1456
This class stores a list of directories that can be searched, in order, to locate a particular file...
Definition: dSearchPath.h:32
string to_os_specific() const
Converts the filename from our generic Unix-like convention (forward slashes starting with the root a...
Definition: filename.cxx:1196
bool exists() const
Returns true if the filename exists on the disk, false otherwise.
Definition: filename.cxx:1356
This class can be used to test for string matches against standard Unix-shell filename globbing conve...
Definition: globPattern.h:37
static Filename from_os_specific(const string &os_specific, Type type=T_general)
This named constructor returns a Panda-style filename (that is, using forward slashes, and no drive letter) based on the supplied filename string that describes a filename in the local system conventions (for instance, on Windows, it may use backslashes or begin with a drive letter and a colon).
Definition: filename.cxx:332