Ver código fonte

Merge branch 'master' into cmake

Sam Edwards 7 anos atrás
pai
commit
3b194c75e4
100 arquivos alterados com 7279 adições e 3818 exclusões
  1. 1 1
      .travis.yml
  2. 0 12
      direct/src/actor/Actor.py
  3. 72 0
      direct/src/dcparser/dcArrayParameter.cxx
  4. 4 0
      direct/src/dcparser/dcArrayParameter.h
  5. 5 5
      direct/src/dcparser/dcAtomicField.cxx
  6. 1 1
      direct/src/dcparser/dcAtomicField.h
  7. 9 2
      direct/src/dcparser/dcClass.cxx
  8. 1 1
      direct/src/dcparser/dcClass.h
  9. 3 3
      direct/src/dcparser/dcField.I
  10. 10 9
      direct/src/dcparser/dcField.cxx
  11. 6 6
      direct/src/dcparser/dcField.h
  12. 160 142
      direct/src/dcparser/dcLexer.cxx.prebuilt
  13. 31 31
      direct/src/dcparser/dcLexer.lxx
  14. 107 9
      direct/src/dcparser/dcPacker.I
  15. 9 9
      direct/src/dcparser/dcPacker.cxx
  16. 13 6
      direct/src/dcparser/dcPacker.h
  17. 16 0
      direct/src/dcparser/dcPackerInterface.cxx
  18. 7 2
      direct/src/dcparser/dcPackerInterface.h
  19. 434 547
      direct/src/dcparser/dcParser.cxx.prebuilt
  20. 66 63
      direct/src/dcparser/dcParser.h.prebuilt
  21. 28 27
      direct/src/dcparser/dcParser.yxx
  22. 2 0
      direct/src/dcparser/dcParserDefs.h
  23. 119 0
      direct/src/dcparser/dcSimpleParameter.cxx
  24. 4 0
      direct/src/dcparser/dcSimpleParameter.h
  25. 8 7
      direct/src/dcparser/dcSwitch.cxx
  26. 6 6
      direct/src/dcparser/dcSwitch.h
  27. 12 0
      direct/src/dcparser/hashGenerator.cxx
  28. 2 0
      direct/src/dcparser/hashGenerator.h
  29. 16 2
      direct/src/directscripts/Doxyfile.cxx
  30. 3 1
      direct/src/directscripts/Doxyfile.python
  31. 79 51
      direct/src/directscripts/extract_docs.py
  32. 885 108
      direct/src/dist/FreezeTool.py
  33. 0 0
      direct/src/dist/__init__.py
  34. 1387 0
      direct/src/dist/commands.py
  35. 908 0
      direct/src/dist/pefile.py
  36. 1 1
      direct/src/dist/pfreeze.py
  37. 1 1
      direct/src/distributed/cConnectionRepository.cxx
  38. 22 19
      direct/src/filter/CommonFilters.py
  39. 3 1
      direct/src/filter/filterBloomI.py
  40. 2 0
      direct/src/filter/filterBloomX.py
  41. 2 0
      direct/src/filter/filterBloomY.py
  42. 2 0
      direct/src/filter/filterBlurX.py
  43. 2 1
      direct/src/filter/filterBlurY.py
  44. 2 1
      direct/src/filter/filterCopy.py
  45. 2 1
      direct/src/filter/filterDown4.py
  46. 1 1
      direct/src/p3d/Packager.py
  47. 0 4
      direct/src/p3d/panda3d.pdef
  48. 63 0
      direct/src/showbase/ShowBase.py
  49. 7 10
      direct/src/showbase/Transitions.py
  50. 0 10
      direct/src/stdpy/thread.py
  51. 0 1
      doc/INSTALL
  52. 148 0
      doc/ReleaseNotes
  53. 155 125
      dtool/metalibs/dtoolconfig/pydtool.cxx
  54. 58 2
      dtool/src/cppparser/cppExpression.cxx
  55. 1 0
      dtool/src/cppparser/cppExpression.h
  56. 122 3
      dtool/src/dtoolutil/globPattern.cxx
  57. 2 0
      dtool/src/dtoolutil/globPattern.h
  58. 161 161
      dtool/src/interrogate/interfaceMakerPythonNative.cxx
  59. 4 1
      dtool/src/interrogate/interrogate.cxx
  60. 1 0
      dtool/src/interrogate/interrogateBuilder.cxx
  61. 10 0
      dtool/src/interrogatedb/dtool_super_base.cxx
  62. 12 0
      dtool/src/interrogatedb/interrogate_interface.cxx
  63. 3 0
      dtool/src/interrogatedb/interrogate_interface.h
  64. 37 45
      dtool/src/interrogatedb/py_panda.cxx
  65. 0 3
      dtool/src/interrogatedb/py_panda.h
  66. 5 0
      dtool/src/parser-inc/XInput.h
  67. 2 0
      dtool/src/parser-inc/windows.h
  68. 90 3
      dtool/src/prc/configPageManager.cxx
  69. 275 174
      makepanda/installer.nsi
  70. 29 23
      makepanda/installpanda.py
  71. 1059 0
      makepanda/makepackage.py
  72. 235 961
      makepanda/makepanda.py
  73. 3 22
      makepanda/makepanda.vcproj
  74. 70 1
      makepanda/makepandacore.py
  75. 171 53
      makepanda/makewheel.py
  76. 1 1
      makepanda/test_imports.py
  77. 1 1
      panda/src/audiotraits/openalAudioSound.cxx
  78. 0 47
      panda/src/awesomium/AwMouseAndKeyboard.cxx
  79. 0 8
      panda/src/awesomium/ReadMe.txt
  80. 0 194
      panda/src/awesomium/WebBrowserTexture.cxx
  81. 0 77
      panda/src/awesomium/WebBrowserTexture.h
  82. 0 48
      panda/src/awesomium/awWebCore.I
  83. 0 56
      panda/src/awesomium/awWebCore.cxx
  84. 0 94
      panda/src/awesomium/awWebCore.h
  85. 0 82
      panda/src/awesomium/awWebView.I
  86. 0 74
      panda/src/awesomium/awWebView.cxx
  87. 0 131
      panda/src/awesomium/awWebView.h
  88. 0 80
      panda/src/awesomium/awWebViewListener.cxx
  89. 0 123
      panda/src/awesomium/awWebViewListener.h
  90. 0 21
      panda/src/awesomium/awesomium_includes.h
  91. 0 51
      panda/src/awesomium/config_awesomium.cxx
  92. 0 26
      panda/src/awesomium/config_awesomium.h
  93. 0 4
      panda/src/awesomium/pandaawesomium_composite1.cxx
  94. 6 6
      panda/src/bullet/bulletBodyNode.cxx
  95. 6 6
      panda/src/bullet/bulletCapsuleShape.cxx
  96. 2 2
      panda/src/bullet/bulletCapsuleShape.h
  97. 6 3
      panda/src/bullet/bulletVehicle.cxx
  98. 8 0
      panda/src/cocoadisplay/cocoaGraphicsStateGuardian.h
  99. 68 4
      panda/src/cocoadisplay/cocoaGraphicsStateGuardian.mm
  100. 4 0
      panda/src/cocoadisplay/cocoaGraphicsWindow.h

+ 1 - 1
.travis.yml

@@ -108,7 +108,7 @@ script:
 notifications:
   irc:
     channels:
-    - "chat.freenode.net#panda3d"
+      - secure: "jfwHT9RHAVOGRGTMY8TpYKJI6rq8nFoIj41Y0soZdJQNWtSSFEK9AyzZeMY+2dHga7cR/X+/0NWZ2ehhedTnd9FvlzOnMWWC3K0I/b3XWbEdVEqIZnggFkKGqs82Gy3omguRC63yWupeJCcSCckIhoWbLzWy6xV8lF5WC80iXi8="
     on_success: change
     on_failure: always
     use_notice: true

+ 0 - 12
direct/src/actor/Actor.py

@@ -653,15 +653,11 @@ class Actor(DirectObject, NodePath):
         """
         # make sure we don't call this twice in a row
         # and pollute the the switches dictionary
-##         sortedKeys = list(self.switches.keys())
-##         sortedKeys.sort()
         child = self.__LODNode.find(str(lodName))
         index = self.__LODNode.node().findChild(child.node())
         self.__LODNode.node().forceSwitch(index)
 
     def printLOD(self):
-##         sortedKeys = list(self.switches.keys())
-##         sortedKeys.sort()
         sortedKeys = self.__sortedLODNames
         for eachLod in sortedKeys:
             print("python switches for %s: in: %d, out %d" % (eachLod,
@@ -679,12 +675,6 @@ class Actor(DirectObject, NodePath):
         """
         Restore all switch distance info (usually after a useLOD call)"""
         self.__LODNode.node().clearForceSwitch()
-##         sortedKeys = list(self.switches.keys())
-##         sortedKeys.sort()
-##         for eachLod in sortedKeys:
-##             index = sortedKeys.index(eachLod)
-##             self.__LODNode.node().setSwitch(index, self.switches[eachLod][0],
-##                                      self.switches[eachLod][1])
 
     def addLOD(self, lodName, inDist=0, outDist=0, center=None):
         """addLOD(self, string)
@@ -706,8 +696,6 @@ class Actor(DirectObject, NodePath):
         # save the switch distance info
         self.switches[lodName] = [inDist, outDist]
         # add the switch distance info
-##         sortedKeys = list(self.switches.keys())
-##         sortedKeys.sort()
         self.__LODNode.node().setSwitch(self.getLODIndex(lodName), inDist, outDist)
 
     def getLODIndex(self, lodName):

+ 72 - 0
direct/src/dcparser/dcArrayParameter.cxx

@@ -262,6 +262,38 @@ pack_string(DCPackData &pack_data, const string &value,
   }
 }
 
+/**
+ * Packs the indicated numeric or string value into the stream.
+ */
+void DCArrayParameter::
+pack_blob(DCPackData &pack_data, const vector_uchar &value,
+          bool &pack_error, bool &range_error) const {
+  // We can only pack a string if the array element type is char or int8.
+  DCSimpleParameter *simple_type = _element_type->as_simple_parameter();
+  if (simple_type == nullptr) {
+    pack_error = true;
+    return;
+  }
+
+  size_t blob_size = value.size();
+
+  switch (simple_type->get_type()) {
+  case ST_char:
+  case ST_uint8:
+  case ST_int8:
+    _array_size_range.validate(blob_size, range_error);
+    if (_num_length_bytes != 0) {
+      nassertv(_num_length_bytes == 2);
+      do_pack_uint16(pack_data.get_write_pointer(2), blob_size);
+    }
+    pack_data.append_data((const char *)value.data(), blob_size);
+    break;
+
+  default:
+    pack_error = true;
+  }
+}
+
 /**
  * Packs the arrayParameter's specified default value (or a sensible default
  * if no value is specified) into the stream.  Returns true if the default
@@ -339,6 +371,46 @@ unpack_string(const char *data, size_t length, size_t &p, string &value,
   }
 }
 
+/**
+ * Unpacks the current numeric or string value from the stream.
+ */
+void DCArrayParameter::
+unpack_blob(const char *data, size_t length, size_t &p, vector_uchar &value,
+            bool &pack_error, bool &range_error) const {
+  // We can only unpack a string if the array element type is char or int8.
+  DCSimpleParameter *simple_type = _element_type->as_simple_parameter();
+  if (simple_type == nullptr) {
+    pack_error = true;
+    return;
+  }
+
+  size_t blob_size;
+
+  switch (simple_type->get_type()) {
+  case ST_char:
+  case ST_uint8:
+  case ST_int8:
+    if (_num_length_bytes != 0) {
+      blob_size = do_unpack_uint16(data + p);
+      p += 2;
+    } else {
+      nassertv(_array_size >= 0);
+      blob_size = _array_size;
+    }
+    if (p + blob_size > length) {
+      pack_error = true;
+      return;
+    }
+    value = vector_uchar((const unsigned char *)data + p,
+                         (const unsigned char *)data + p + blob_size);
+    p += blob_size;
+    break;
+
+  default:
+    pack_error = true;
+  }
+}
+
 /**
  * Returns true if the other interface is bitwise the same as this one--that
  * is, a uint32 only matches a uint32, etc.  Names of components, and range

+ 4 - 0
direct/src/dcparser/dcArrayParameter.h

@@ -51,9 +51,13 @@ public:
   virtual void generate_hash(HashGenerator &hashgen) const;
   virtual void pack_string(DCPackData &pack_data, const std::string &value,
                            bool &pack_error, bool &range_error) const;
+  virtual void pack_blob(DCPackData &pack_data, const vector_uchar &value,
+                         bool &pack_error, bool &range_error) const;
   virtual bool pack_default_value(DCPackData &pack_data, bool &pack_error) const;
   virtual void unpack_string(const char *data, size_t length, size_t &p,
                              std::string &value, bool &pack_error, bool &range_error) const;
+  virtual void unpack_blob(const char *data, size_t length, size_t &p,
+                           vector_uchar &value, bool &pack_error, bool &range_error) const;
 
 protected:
   virtual bool do_check_match(const DCPackerInterface *other) const;

+ 5 - 5
direct/src/dcparser/dcAtomicField.cxx

@@ -88,11 +88,11 @@ get_element(int n) const {
  * If the element is an array-type element, the returned value will include
  * the two-byte length preceding the array data.
  *
- * This is deprecated; use get_element() instead.
+ * @deprecated use get_element() instead.
  */
-string DCAtomicField::
+vector_uchar DCAtomicField::
 get_element_default(int n) const {
-  nassertr(n >= 0 && n < (int)_elements.size(), string());
+  nassertr(n >= 0 && n < (int)_elements.size(), vector_uchar());
   return _elements[n]->get_default_value();
 }
 
@@ -100,7 +100,7 @@ get_element_default(int n) const {
  * Returns true if the nth element of the field has a default value specified,
  * false otherwise.
  *
- * This is deprecated; use get_element() instead.
+ * @deprecated use get_element() instead.
  */
 bool DCAtomicField::
 has_element_default(int n) const {
@@ -113,7 +113,7 @@ has_element_default(int n) const {
  * for documentary purposes; it does not generally affect operation.  If a
  * name is not specified, this will be the empty string.
  *
- * This method is deprecated; use get_element()->get_name() instead.
+ * @deprecated use get_element()->get_name() instead.
  */
 string DCAtomicField::
 get_element_name(int n) const {

+ 1 - 1
direct/src/dcparser/dcAtomicField.h

@@ -40,7 +40,7 @@ PUBLISHED:
   DCParameter *get_element(int n) const;
 
   // These five methods are deprecated and will be removed soon.
-  std::string get_element_default(int n) const;
+  vector_uchar get_element_default(int n) const;
   bool has_element_default(int n) const;
   std::string get_element_name(int n) const;
   DCSubatomicType get_element_type(int n) const;

+ 9 - 2
direct/src/dcparser/dcClass.cxx

@@ -587,7 +587,7 @@ receive_update_other(PyObject *distobj, DatagramIterator &di) const {
  */
 void DCClass::
 direct_update(PyObject *distobj, const string &field_name,
-              const string &value_blob) {
+              const vector_uchar &value_blob) {
   DCField *field = get_field_by_name(field_name);
   nassertv_always(field != nullptr);
 
@@ -606,7 +606,14 @@ direct_update(PyObject *distobj, const string &field_name,
 void DCClass::
 direct_update(PyObject *distobj, const string &field_name,
               const Datagram &datagram) {
-  direct_update(distobj, field_name, datagram.get_message());
+  DCField *field = get_field_by_name(field_name);
+  nassertv_always(field != nullptr);
+
+  DCPacker packer;
+  packer.set_unpack_data((const char *)datagram.get_data(), datagram.get_length(), false);
+  packer.begin_unpack(field);
+  field->receive_update(packer, distobj);
+  packer.end_unpack();
 }
 #endif  // HAVE_PYTHON
 

+ 1 - 1
direct/src/dcparser/dcClass.h

@@ -95,7 +95,7 @@ PUBLISHED:
   void receive_update_other(PyObject *distobj, DatagramIterator &di) const;
 
   void direct_update(PyObject *distobj, const std::string &field_name,
-                     const std::string &value_blob);
+                     const vector_uchar &value_blob);
   void direct_update(PyObject *distobj, const std::string &field_name,
                      const Datagram &datagram);
   bool pack_required_field(Datagram &datagram, PyObject *distobj,

+ 3 - 3
direct/src/dcparser/dcField.I

@@ -42,7 +42,7 @@ has_default_value() const {
  * explicitly set (e.g.  has_default_value() returns true), returns that
  * value; otherwise, returns an implicit default for the field.
  */
-INLINE const std::string &DCField::
+INLINE const vector_uchar &DCField::
 get_default_value() const {
   if (_default_value_stale) {
     ((DCField *)this)->refresh_default_value();
@@ -172,8 +172,8 @@ set_class(DCClass *dclass) {
  * Establishes a default value for this field.
  */
 INLINE void DCField::
-set_default_value(const std::string &default_value) {
-  _default_value = default_value;
+set_default_value(vector_uchar default_value) {
+  _default_value = std::move(default_value);
   _has_default_value = true;
   _default_value_stale = false;
 }

+ 10 - 9
direct/src/dcparser/dcField.cxx

@@ -162,7 +162,7 @@ as_parameter() const {
  * is an error.
  */
 string DCField::
-format_data(const string &packed_data, bool show_field_names) {
+format_data(const vector_uchar &packed_data, bool show_field_names) {
   DCPacker packer;
   packer.set_unpack_data(packed_data);
   packer.begin_unpack(this);
@@ -178,20 +178,20 @@ format_data(const string &packed_data, bool show_field_names) {
  * above) that represents the value of this field, parse the string and return
  * the corresponding packed data.  Returns empty string if there is an error.
  */
-string DCField::
+vector_uchar DCField::
 parse_string(const string &formatted_string) {
   DCPacker packer;
   packer.begin_pack(this);
   if (!packer.parse_and_pack(formatted_string)) {
     // Parse error.
-    return string();
+    return vector_uchar();
   }
   if (!packer.end_pack()) {
     // Data type mismatch.
-    return string();
+    return vector_uchar();
   }
 
-  return packer.get_string();
+  return packer.get_bytes();
 }
 
 /**
@@ -200,7 +200,7 @@ parse_string(const string &formatted_string) {
  * record.  Returns true if all fields are valid, false otherwise.
  */
 bool DCField::
-validate_ranges(const string &packed_data) const {
+validate_ranges(const vector_uchar &packed_data) const {
   DCPacker packer;
   packer.set_unpack_data(packed_data);
   packer.begin_unpack(this);
@@ -209,7 +209,7 @@ validate_ranges(const string &packed_data) const {
     return false;
   }
 
-  return (packer.get_num_unpacked_bytes() == packed_data.length());
+  return (packer.get_num_unpacked_bytes() == packed_data.size());
 }
 
 #ifdef HAVE_PYTHON
@@ -488,7 +488,7 @@ pack_default_value(DCPackData &pack_data, bool &) const {
   // The default behavior is to pack the default value if we got it;
   // otherwise, to return false and let the packer visit our nested elements.
   if (!_default_value_stale) {
-    pack_data.append_data(_default_value.data(), _default_value.length());
+    pack_data.append_data((const char *)_default_value.data(), _default_value.size());
     return true;
   }
 
@@ -566,7 +566,8 @@ refresh_default_value() {
   if (!packer.end_pack()) {
     std::cerr << "Error while packing default value for " << get_name() << "\n";
   } else {
-    _default_value.assign(packer.get_data(), packer.get_length());
+    const unsigned char *data = (const unsigned char *)packer.get_data();
+    _default_value = vector_uchar(data, data + packer.get_length());
   }
   _default_value_stale = false;
 }

+ 6 - 6
direct/src/dcparser/dcField.h

@@ -53,13 +53,13 @@ PUBLISHED:
   virtual DCParameter *as_parameter();
   virtual const DCParameter *as_parameter() const;
 
-  std::string format_data(const std::string &packed_data, bool show_field_names = true);
-  std::string parse_string(const std::string &formatted_string);
+  std::string format_data(const vector_uchar &packed_data, bool show_field_names = true);
+  vector_uchar parse_string(const std::string &formatted_string);
 
-  bool validate_ranges(const std::string &packed_data) const;
+  bool validate_ranges(const vector_uchar &packed_data) const;
 
   INLINE bool has_default_value() const;
-  INLINE const std::string &get_default_value() const;
+  INLINE const vector_uchar &get_default_value() const;
 
   INLINE bool is_bogus_field() const;
 
@@ -98,7 +98,7 @@ public:
 
   INLINE void set_number(int number);
   INLINE void set_class(DCClass *dclass);
-  INLINE void set_default_value(const std::string &default_value);
+  INLINE void set_default_value(vector_uchar default_value);
 
 #ifdef HAVE_PYTHON
   static std::string get_pystr(PyObject *value);
@@ -115,7 +115,7 @@ protected:
   bool _bogus_field;
 
 private:
-  std::string _default_value;
+  vector_uchar _default_value;
 
 #ifdef WITHIN_PANDA
   PStatCollector _field_update_pcollector;

Diferenças do arquivo suprimidas por serem muito extensas
+ 160 - 142
direct/src/dcparser/dcLexer.cxx.prebuilt


+ 31 - 31
direct/src/dcparser/dcLexer.lxx

@@ -1,7 +1,7 @@
 /*
 // Filename: dcLexer.lxx
 // Created by:  drose (05Oct00)
-// 
+//
 ////////////////////////////////////////////////////////////////////
 */
 
@@ -103,12 +103,12 @@ dcyyerror(const std::string &msg) {
   if (!dc_filename.empty()) {
     cerr << " in " << dc_filename;
   }
-  cerr 
+  cerr
     << " at line " << line_number << ", column " << col_number << ":\n"
     << current_line << "\n";
-  indent(cerr, col_number-1) 
+  indent(cerr, col_number-1)
     << "^\n" << msg << "\n\n";
-  
+
   error_count++;
 }
 
@@ -120,10 +120,10 @@ dcyywarning(const std::string &msg) {
   if (!dc_filename.empty()) {
     cerr << " in " << dc_filename;
   }
-  cerr 
+  cerr
     << " at line " << line_number << ", column " << col_number << ":\n"
     << current_line << "\n";
-  indent(cerr, col_number-1) 
+  indent(cerr, col_number-1)
     << "^\n" << msg << "\n\n";
 
   warning_count++;
@@ -313,9 +313,9 @@ scan_quoted_string(char quote_mark) {
 
 // scan_hex_string reads a string of hexadecimal digits delimited by
 // angle brackets and returns the representative string.
-static std::string
+static vector_uchar
 scan_hex_string() {
-  std::string result;
+  vector_uchar result;
 
   // We don't touch the current line number and column number during
   // scanning, so that if we detect an error while scanning the string
@@ -345,24 +345,24 @@ scan_hex_string() {
       line_number = line;
       col_number = col;
       dcyyerror("Invalid hex digit.");
-      return std::string();
+      return vector_uchar();
     }
 
     odd = !odd;
     if (odd) {
       last = value;
     } else {
-      result += (char)((last << 4) | value);
+      result.push_back((unsigned char)((last << 4) | value));
     }
     c = read_char(line, col);
   }
 
   if (c == EOF) {
     dcyyerror("This hex string is unterminated.");
-    return std::string();
+    return vector_uchar();
   } else if (odd) {
     dcyyerror("Odd number of hex digits.");
-    return std::string();
+    return vector_uchar();
   }
 
   line_number = line;
@@ -382,7 +382,7 @@ eat_c_comment() {
   int col = col_number;
 
   int c, last_c;
-  
+
   last_c = '\0';
   c = read_char(line, col);
   while (c != EOF && !(last_c == '*' && c == '/')) {
@@ -440,12 +440,12 @@ REALNUM              ([+-]?(([0-9]+[.])|([0-9]*[.][0-9]+))([eE][+-]?[0-9]+)?)
   yyless(1);
 }
 
-[ \t\r] { 
+[ \t\r] {
   // Eat whitespace.
   accept();
 }
 
-"//".* { 
+"//".* {
   // Eat C++-style comments.
   accept();
 }
@@ -453,7 +453,7 @@ REALNUM              ([+-]?(([0-9]+[.])|([0-9]*[.][0-9]+))([eE][+-]?[0-9]+)?)
 "/*" {
   // Eat C-style comments.
   accept();
-  eat_c_comment(); 
+  eat_c_comment();
 }
 
 
@@ -607,7 +607,7 @@ REALNUM              ([+-]?(([0-9]+[.])|([0-9]*[.][0-9]+))([eE][+-]?[0-9]+)?)
   return KW_CHAR;
 }
 
-{UNSIGNED_INTEGERNUM} { 
+{UNSIGNED_INTEGERNUM} {
   // An unsigned integer number.
   accept();
 
@@ -626,11 +626,11 @@ REALNUM              ([+-]?(([0-9]+[.])|([0-9]*[.][0-9]+))([eE][+-]?[0-9]+)?)
     dcyylval.u.uint64 = next_value + (*p - '0');
     ++p;
   }
-  
+
   return UNSIGNED_INTEGER;
 }
 
-{SIGNED_INTEGERNUM} { 
+{SIGNED_INTEGERNUM} {
   // A signed integer number.
   accept();
 
@@ -671,14 +671,14 @@ REALNUM              ([+-]?(([0-9]+[.])|([0-9]*[.][0-9]+))([eE][+-]?[0-9]+)?)
       dcyyerror("Number out of range.");
       dcyylval.u.int64 = 1;
     }
-  }    
-  
+  }
+
   return SIGNED_INTEGER;
 }
 
 {UNSIGNED_HEXNUM} {
   // A hexadecimal integer number.
-  accept(); 
+  accept();
 
   // As above, we'll decode the hex string by hand.
   dcyylval.str = dcyytext;
@@ -700,15 +700,15 @@ REALNUM              ([+-]?(([0-9]+[.])|([0-9]*[.][0-9]+))([eE][+-]?[0-9]+)?)
     ++p;
   }
 
-  return UNSIGNED_INTEGER; 
+  return UNSIGNED_INTEGER;
 }
 
-{REALNUM} { 
+{REALNUM} {
   // A floating-point number.
-  accept(); 
-  dcyylval.u.real = patof(dcyytext); 
+  accept();
+  dcyylval.u.real = patof(dcyytext);
   dcyylval.str = dcyytext;
-  return REAL; 
+  return REAL;
 }
 
 ["] {
@@ -728,11 +728,11 @@ REALNUM              ([+-]?(([0-9]+[.])|([0-9]*[.][0-9]+))([eE][+-]?[0-9]+)?)
 [<] {
   // Long hex string.
   accept();
-  dcyylval.str = scan_hex_string();
+  dcyylval.bytes = scan_hex_string();
   return HEX_STRING;
 }
 
-[A-Za-z_][A-Za-z_0-9]* { 
+[A-Za-z_][A-Za-z_0-9]* {
   // Identifier or keyword.
   accept();
   dcyylval.str = dcyytext;
@@ -750,7 +750,7 @@ REALNUM              ([+-]?(([0-9]+[.])|([0-9]*[.][0-9]+))([eE][+-]?[0-9]+)?)
 
 . {
   // Send any other printable character as itself.
-  accept(); 
+  accept();
   return dcyytext[0];
 }
-  
+

+ 107 - 9
direct/src/dcparser/dcPacker.I

@@ -216,17 +216,31 @@ pack_string(const std::string &value) {
   }
 }
 
+/**
+ * Packs the indicated numeric or string value into the stream.
+ */
+INLINE void DCPacker::
+pack_blob(const vector_uchar &value) {
+  nassertv(_mode == M_pack || _mode == M_repack);
+  if (_current_field == nullptr) {
+    _pack_error = true;
+  } else {
+    _current_field->pack_blob(_pack_data, value, _pack_error, _range_error);
+    advance();
+  }
+}
+
 /**
  * Adds the indicated string value into the stream, representing a single pre-
  * packed field element, or a whole group of field elements at once.
  */
 INLINE void DCPacker::
-pack_literal_value(const std::string &value) {
+pack_literal_value(const vector_uchar &value) {
   nassertv(_mode == M_pack || _mode == M_repack);
   if (_current_field == nullptr) {
     _pack_error = true;
   } else {
-    _pack_data.append_data(value.data(), value.length());
+    _pack_data.append_data((const char *)value.data(), value.size());
     advance();
   }
 }
@@ -345,16 +359,36 @@ unpack_string() {
   return value;
 }
 
+/**
+ * Unpacks the current binary data value from the stream.
+ */
+INLINE vector_uchar DCPacker::
+unpack_blob() {
+  vector_uchar value;
+  nassertr(_mode == M_unpack, value);
+  if (_current_field == nullptr) {
+    _pack_error = true;
+
+  } else {
+    _current_field->unpack_blob(_unpack_data, _unpack_length, _unpack_p,
+                                 value, _pack_error, _range_error);
+    advance();
+  }
+
+  return value;
+}
+
 /**
  * Returns the literal string that represents the packed value of the current
  * field, and advances the field pointer.
  */
-INLINE std::string DCPacker::
+INLINE vector_uchar DCPacker::
 unpack_literal_value() {
   size_t start = _unpack_p;
   unpack_skip();
-  nassertr(_unpack_p >= start, std::string());
-  return std::string(_unpack_data + start, _unpack_p - start);
+  nassertr(_unpack_p >= start, vector_uchar());
+  return vector_uchar((const unsigned char *)_unpack_data + start,
+                      (const unsigned char *)_unpack_data + _unpack_p);
 }
 
 /**
@@ -453,16 +487,33 @@ unpack_string(std::string &value) {
   }
 }
 
+/**
+ * Unpacks the current numeric or string value from the stream.
+ */
+INLINE void DCPacker::
+unpack_blob(vector_uchar &value) {
+  nassertv(_mode == M_unpack);
+  if (_current_field == nullptr) {
+    _pack_error = true;
+
+  } else {
+    _current_field->unpack_blob(_unpack_data, _unpack_length, _unpack_p,
+                                value, _pack_error, _range_error);
+    advance();
+  }
+}
+
 /**
  * Returns the literal string that represents the packed value of the current
  * field, and advances the field pointer.
  */
 INLINE void DCPacker::
-unpack_literal_value(std::string &value) {
+unpack_literal_value(vector_uchar &value) {
   size_t start = _unpack_p;
   unpack_skip();
   nassertv(_unpack_p >= start);
-  value.assign(_unpack_data + start, _unpack_p - start);
+  value = vector_uchar((const unsigned char *)_unpack_data + start,
+                       (const unsigned char *)_unpack_data + _unpack_p);
 }
 
 /**
@@ -541,6 +592,15 @@ get_string() const {
   return _pack_data.get_string();
 }
 
+/**
+ * Returns the packed data buffer as a bytes object.  Also see get_data().
+ */
+INLINE vector_uchar DCPacker::
+get_bytes() const {
+  const unsigned char *p = (const unsigned char *)_pack_data.get_data();
+  return vector_uchar(p, p + _pack_data.get_length());
+}
+
 /**
  * Returns the total number of bytes in the unpack data buffer.  This is the
  * buffer used when unpacking; it is separate from the pack data returned by
@@ -601,9 +661,9 @@ take_data() {
  * between packing sessions.
  */
 INLINE void DCPacker::
-append_data(const char *buffer, size_t size) {
+append_data(const unsigned char *buffer, size_t size) {
   nassertv(_mode == M_idle);
-  _pack_data.append_data(buffer, size);
+  _pack_data.append_data((const char *)buffer, size);
 }
 
 /**
@@ -728,6 +788,16 @@ raw_pack_string(const std::string &value) {
   _pack_data.append_data(value.data(), value.length());
 }
 
+/**
+ * Packs the data into the buffer between packing sessions.
+ */
+INLINE void DCPacker::
+raw_pack_blob(const vector_uchar &value) {
+  nassertv(_mode == M_idle);
+  DCPackerInterface::do_pack_uint16(_pack_data.get_write_pointer(2), value.size());
+  _pack_data.append_data((const char *)value.data(), value.size());
+}
+
 /**
  * Unpacks the data from the buffer between unpacking sessions.
  */
@@ -870,6 +940,16 @@ raw_unpack_string() {
   return value;
 }
 
+/**
+ * Unpacks the data from the buffer between unpacking sessions.
+ */
+INLINE vector_uchar DCPacker::
+raw_unpack_blob() {
+  vector_uchar value;
+  raw_unpack_blob(value);
+  return value;
+}
+
 /**
  * Unpacks the data from the buffer between unpacking sessions.
  */
@@ -971,6 +1051,24 @@ raw_unpack_string(std::string &value) {
   _unpack_p += string_length;
 }
 
+/**
+ * Unpacks the data from the buffer between unpacking sessions.
+ */
+INLINE void DCPacker::
+raw_unpack_blob(vector_uchar &value) {
+  nassertv(_mode == M_idle && _unpack_data != nullptr);
+  unsigned int blob_size = raw_unpack_uint16();
+
+  if (_unpack_p + blob_size > _unpack_length) {
+    _pack_error = true;
+    return;
+  }
+
+  const unsigned char *p = (const unsigned char *)_unpack_data + _unpack_p;
+  value = vector_uchar(p, p + blob_size);
+  _unpack_p += blob_size;
+}
+
 /**
  * Advances to the next field after a call to pack_value() or pop().
  */

+ 9 - 9
direct/src/dcparser/dcPacker.cxx

@@ -114,12 +114,12 @@ end_pack() {
  * version of begin_unpack() that takes only one parameter.
  */
 void DCPacker::
-set_unpack_data(const string &data) {
+set_unpack_data(const vector_uchar &data) {
   nassertv(_mode == M_idle);
 
-  char *buffer = new char[data.length()];
-  memcpy(buffer, data.data(), data.length());
-  set_unpack_data(buffer, data.length(), true);
+  char *buffer = new char[data.size()];
+  memcpy(buffer, data.data(), data.size());
+  set_unpack_data(buffer, data.size(), true);
 }
 
 /**
@@ -721,11 +721,11 @@ pack_object(PyObject *object) {
       pack_string(string(buffer, length));
     }
   } else if (PyBytes_Check(object)) {
-    char *buffer;
+    const unsigned char *buffer;
     Py_ssize_t length;
-    PyBytes_AsStringAndSize(object, &buffer, &length);
+    PyBytes_AsStringAndSize(object, (char **)&buffer, &length);
     if (buffer) {
-      pack_string(string(buffer, length));
+      pack_blob(vector_uchar(buffer, buffer + length));
     }
 #else
   } else if (PyString_Check(object) || PyUnicode_Check(object)) {
@@ -1118,9 +1118,9 @@ enquote_string(ostream &out, char quote_mark, const string &str) {
  * Outputs the indicated string as a hex constant.
  */
 void DCPacker::
-output_hex_string(ostream &out, const string &str) {
+output_hex_string(ostream &out, const vector_uchar &str) {
   out << '<';
-  for (string::const_iterator pi = str.begin();
+  for (vector_uchar::const_iterator pi = str.begin();
        pi != str.end();
        ++pi) {
     char buffer[10];

+ 13 - 6
direct/src/dcparser/dcPacker.h

@@ -41,7 +41,7 @@ PUBLISHED:
   void begin_pack(const DCPackerInterface *root);
   bool end_pack();
 
-  void set_unpack_data(const std::string &data);
+  void set_unpack_data(const vector_uchar &data);
 public:
   void set_unpack_data(const char *unpack_data, size_t unpack_length,
                        bool owns_unpack_data);
@@ -75,7 +75,8 @@ PUBLISHED:
   INLINE void pack_int64(int64_t value);
   INLINE void pack_uint64(uint64_t value);
   INLINE void pack_string(const std::string &value);
-  INLINE void pack_literal_value(const std::string &value);
+  INLINE void pack_blob(const vector_uchar &value);
+  INLINE void pack_literal_value(const vector_uchar &value);
   void pack_default_value();
 
   INLINE double unpack_double();
@@ -84,7 +85,8 @@ PUBLISHED:
   INLINE int64_t unpack_int64();
   INLINE uint64_t unpack_uint64();
   INLINE std::string unpack_string();
-  INLINE std::string unpack_literal_value();
+  INLINE vector_uchar unpack_blob();
+  INLINE vector_uchar unpack_literal_value();
   void unpack_validate();
   void unpack_skip();
 
@@ -97,7 +99,8 @@ public:
   INLINE void unpack_int64(int64_t &value);
   INLINE void unpack_uint64(uint64_t &value);
   INLINE void unpack_string(std::string &value);
-  INLINE void unpack_literal_value(std::string &value);
+  INLINE void unpack_blob(vector_uchar &value);
+  INLINE void unpack_literal_value(vector_uchar &value);
 
 PUBLISHED:
 
@@ -119,6 +122,7 @@ PUBLISHED:
 
   INLINE size_t get_length() const;
   INLINE std::string get_string() const;
+  INLINE vector_uchar get_bytes() const;
   INLINE size_t get_unpack_length() const;
   INLINE std::string get_unpack_string() const;
 public:
@@ -126,7 +130,7 @@ public:
   INLINE const char *get_data() const;
   INLINE char *take_data();
 
-  INLINE void append_data(const char *buffer, size_t size);
+  INLINE void append_data(const unsigned char *buffer, size_t size);
   INLINE char *get_write_pointer(size_t size);
 
   INLINE const char *get_unpack_data() const;
@@ -148,6 +152,7 @@ PUBLISHED:
   INLINE void raw_pack_uint64(uint64_t value);
   INLINE void raw_pack_float64(double value);
   INLINE void raw_pack_string(const std::string &value);
+  INLINE void raw_pack_blob(const vector_uchar &value);
 
 // this is a hack to allw me to get in and out of 32bit Mode Faster need to
 // agree with channel_type in dcbase.h
@@ -165,6 +170,7 @@ PUBLISHED:
   INLINE uint64_t raw_unpack_uint64();
   INLINE double raw_unpack_float64();
   INLINE std::string raw_unpack_string();
+  INLINE vector_uchar raw_unpack_blob();
 
 public:
   INLINE void raw_unpack_int8(int &value);
@@ -177,10 +183,11 @@ public:
   INLINE void raw_unpack_uint64(uint64_t &value);
   INLINE void raw_unpack_float64(double &value);
   INLINE void raw_unpack_string(std::string &value);
+  INLINE void raw_unpack_blob(vector_uchar &value);
 
 public:
   static void enquote_string(std::ostream &out, char quote_mark, const std::string &str);
-  static void output_hex_string(std::ostream &out, const std::string &str);
+  static void output_hex_string(std::ostream &out, const vector_uchar &str);
 
 private:
   INLINE void advance();

+ 16 - 0
direct/src/dcparser/dcPackerInterface.cxx

@@ -248,6 +248,14 @@ pack_string(DCPackData &, const string &, bool &pack_error, bool &) const {
   pack_error = true;
 }
 
+/**
+ * Packs the indicated numeric or string value into the stream.
+ */
+void DCPackerInterface::
+pack_blob(DCPackData &, const vector_uchar &, bool &pack_error, bool &) const {
+  pack_error = true;
+}
+
 /**
  * Packs the field's specified default value (or a sensible default if no
  * value is specified) into the stream.  Returns true if the default value is
@@ -306,6 +314,14 @@ unpack_string(const char *, size_t, size_t &, string &, bool &pack_error, bool &
   pack_error = true;
 }
 
+/**
+ * Unpacks the current numeric or string value from the stream.
+ */
+void DCPackerInterface::
+unpack_blob(const char *, size_t, size_t &, vector_uchar &, bool &pack_error, bool &) const {
+  pack_error = true;
+}
+
 /**
  * Internally unpacks the current numeric or string value and validates it
  * against the type range limits, but does not return the value.  Returns true

+ 7 - 2
direct/src/dcparser/dcPackerInterface.h

@@ -16,6 +16,7 @@
 
 #include "dcbase.h"
 #include "dcSubatomicType.h"
+#include "vector_uchar.h"
 
 class DCFile;
 class DCField;
@@ -37,8 +38,8 @@ enum DCPackType {
 
   // These PackTypes are all fundamental types, and should be packed (or
   // unpacked) with the corresponding call to pack_double(), pack_int(), etc.
-  // PT_blob is the same as PT_string, but implies that the string contains
-  // binary data.
+  // PT_blob is similar to PT_string, except that it contains arbitrary binary
+  // data instead of just UTF-8 text.
   PT_double,
   PT_int,
   PT_uint,
@@ -113,6 +114,8 @@ public:
                            bool &pack_error, bool &range_error) const;
   virtual void pack_string(DCPackData &pack_data, const std::string &value,
                            bool &pack_error, bool &range_error) const;
+  virtual void pack_blob(DCPackData &pack_data, const vector_uchar &value,
+                         bool &pack_error, bool &range_error) const;
   virtual bool pack_default_value(DCPackData &pack_data, bool &pack_error) const;
 
   virtual void unpack_double(const char *data, size_t length, size_t &p,
@@ -127,6 +130,8 @@ public:
                              uint64_t &value, bool &pack_error, bool &range_error) const;
   virtual void unpack_string(const char *data, size_t length, size_t &p,
                              std::string &value, bool &pack_error, bool &range_error) const;
+  virtual void unpack_blob(const char *data, size_t length, size_t &p,
+                           vector_uchar &value, bool &pack_error, bool &range_error) const;
   virtual bool unpack_validate(const char *data, size_t length, size_t &p,
                                bool &pack_error, bool &range_error) const;
   virtual bool unpack_skip(const char *data, size_t length, size_t &p,

Diferenças do arquivo suprimidas por serem muito extensas
+ 434 - 547
direct/src/dcparser/dcParser.cxx.prebuilt


+ 66 - 63
direct/src/dcparser/dcParser.h.prebuilt

@@ -1,20 +1,19 @@
-/* A Bison parser, made by GNU Bison 2.4.2.  */
+/* A Bison parser, made by GNU Bison 3.1.  */
+
+/* Bison interface for Yacc-like parsers in C
+
+   Copyright (C) 1984, 1989-1990, 2000-2015, 2018 Free Software Foundation, Inc.
 
-/* Skeleton interface for Bison's Yacc-like parsers in C
-   
-      Copyright (C) 1984, 1989-1990, 2000-2006, 2009-2010 Free Software
-   Foundation, Inc.
-   
    This program is free software: you can redistribute it and/or modify
    it under the terms of the GNU General Public License as published by
    the Free Software Foundation, either version 3 of the License, or
    (at your option) any later version.
-   
+
    This program is distributed in the hope that it will be useful,
    but WITHOUT ANY WARRANTY; without even the implied warranty of
    MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
    GNU General Public License for more details.
-   
+
    You should have received a copy of the GNU General Public License
    along with this program.  If not, see <http://www.gnu.org/licenses/>.  */
 
@@ -27,66 +26,74 @@
    special exception, which will cause the skeleton and the resulting
    Bison output files to be licensed under the GNU General Public
    License without this special exception.
-   
+
    This special exception was added by the Free Software Foundation in
    version 2.2 of Bison.  */
 
+#ifndef YY_DCYY_BUILT_TMP_DCPARSER_YXX_H_INCLUDED
+# define YY_DCYY_BUILT_TMP_DCPARSER_YXX_H_INCLUDED
+/* Debug traces.  */
+#ifndef YYDEBUG
+# define YYDEBUG 0
+#endif
+#if YYDEBUG
+extern int dcyydebug;
+#endif
 
-/* Tokens.  */
+/* Token type.  */
 #ifndef YYTOKENTYPE
 # define YYTOKENTYPE
-   /* Put the tokens into the symbol table, so that GDB and other debuggers
-      know about them.  */
-   enum yytokentype {
-     UNSIGNED_INTEGER = 258,
-     SIGNED_INTEGER = 259,
-     REAL = 260,
-     STRING = 261,
-     HEX_STRING = 262,
-     IDENTIFIER = 263,
-     KEYWORD = 264,
-     KW_DCLASS = 265,
-     KW_STRUCT = 266,
-     KW_FROM = 267,
-     KW_IMPORT = 268,
-     KW_TYPEDEF = 269,
-     KW_KEYWORD = 270,
-     KW_SWITCH = 271,
-     KW_CASE = 272,
-     KW_DEFAULT = 273,
-     KW_BREAK = 274,
-     KW_INT8 = 275,
-     KW_INT16 = 276,
-     KW_INT32 = 277,
-     KW_INT64 = 278,
-     KW_UINT8 = 279,
-     KW_UINT16 = 280,
-     KW_UINT32 = 281,
-     KW_UINT64 = 282,
-     KW_FLOAT64 = 283,
-     KW_STRING = 284,
-     KW_BLOB = 285,
-     KW_BLOB32 = 286,
-     KW_INT8ARRAY = 287,
-     KW_INT16ARRAY = 288,
-     KW_INT32ARRAY = 289,
-     KW_UINT8ARRAY = 290,
-     KW_UINT16ARRAY = 291,
-     KW_UINT32ARRAY = 292,
-     KW_UINT32UINT8ARRAY = 293,
-     KW_CHAR = 294,
-     START_DC = 295,
-     START_PARAMETER_VALUE = 296,
-     START_PARAMETER_DESCRIPTION = 297
-   };
+  enum yytokentype
+  {
+    UNSIGNED_INTEGER = 258,
+    SIGNED_INTEGER = 259,
+    REAL = 260,
+    STRING = 261,
+    IDENTIFIER = 262,
+    HEX_STRING = 263,
+    KEYWORD = 264,
+    KW_DCLASS = 265,
+    KW_STRUCT = 266,
+    KW_FROM = 267,
+    KW_IMPORT = 268,
+    KW_TYPEDEF = 269,
+    KW_KEYWORD = 270,
+    KW_SWITCH = 271,
+    KW_CASE = 272,
+    KW_DEFAULT = 273,
+    KW_BREAK = 274,
+    KW_INT8 = 275,
+    KW_INT16 = 276,
+    KW_INT32 = 277,
+    KW_INT64 = 278,
+    KW_UINT8 = 279,
+    KW_UINT16 = 280,
+    KW_UINT32 = 281,
+    KW_UINT64 = 282,
+    KW_FLOAT64 = 283,
+    KW_STRING = 284,
+    KW_BLOB = 285,
+    KW_BLOB32 = 286,
+    KW_INT8ARRAY = 287,
+    KW_INT16ARRAY = 288,
+    KW_INT32ARRAY = 289,
+    KW_UINT8ARRAY = 290,
+    KW_UINT16ARRAY = 291,
+    KW_UINT32ARRAY = 292,
+    KW_UINT32UINT8ARRAY = 293,
+    KW_CHAR = 294,
+    START_DC = 295,
+    START_PARAMETER_VALUE = 296,
+    START_PARAMETER_DESCRIPTION = 297
+  };
 #endif
 /* Tokens.  */
 #define UNSIGNED_INTEGER 258
 #define SIGNED_INTEGER 259
 #define REAL 260
 #define STRING 261
-#define HEX_STRING 262
-#define IDENTIFIER 263
+#define IDENTIFIER 262
+#define HEX_STRING 263
 #define KEYWORD 264
 #define KW_DCLASS 265
 #define KW_STRUCT 266
@@ -122,15 +129,11 @@
 #define START_PARAMETER_VALUE 296
 #define START_PARAMETER_DESCRIPTION 297
 
+/* Value type.  */
 
 
-
-#if ! defined YYSTYPE && ! defined YYSTYPE_IS_DECLARED
-
-# define yystype YYSTYPE /* obsolescent; will be withdrawn */
-# define YYSTYPE_IS_DECLARED 1
-#endif
-
 extern YYSTYPE dcyylval;
 
+int dcyyparse (void);
 
+#endif /* !YY_DCYY_BUILT_TMP_DCPARSER_YXX_H_INCLUDED  */

+ 28 - 27
direct/src/dcparser/dcParser.yxx

@@ -57,7 +57,7 @@ dc_init_parser(istream &in, const string &filename, DCFile &file) {
 }
 
 void
-dc_init_parser_parameter_value(istream &in, const string &filename, 
+dc_init_parser_parameter_value(istream &in, const string &filename,
                                DCPacker &packer) {
   dc_file = nullptr;
   current_packer = &packer;
@@ -89,15 +89,16 @@ dc_cleanup_parser() {
 %token <u.uint64> UNSIGNED_INTEGER
 %token <u.int64> SIGNED_INTEGER
 %token <u.real> REAL
-%token <str> STRING HEX_STRING IDENTIFIER
+%token <str> STRING IDENTIFIER
+%token <bytes> HEX_STRING
 %token <u.keyword> KEYWORD
 
-%token KW_DCLASS 
-%token KW_STRUCT 
-%token KW_FROM 
-%token KW_IMPORT 
-%token KW_TYPEDEF 
-%token KW_KEYWORD 
+%token KW_DCLASS
+%token KW_STRUCT
+%token KW_FROM
+%token KW_IMPORT
+%token KW_TYPEDEF
+%token KW_KEYWORD
 %token KW_SWITCH
 %token KW_CASE
 %token KW_DEFAULT
@@ -224,7 +225,7 @@ import:
 {
   dc_file->add_import_module($2);
 }
-        | KW_FROM import_identifier KW_IMPORT 
+        | KW_FROM import_identifier KW_IMPORT
 {
   dc_file->add_import_module($2);
 }
@@ -255,7 +256,7 @@ typedef_decl:
 {
   if ($2 != nullptr) {
     DCTypedef *dtypedef = new DCTypedef($2);
-    
+
     if (!dc_file->add_typedef(dtypedef)) {
       DCTypedef *old_typedef = dc_file->get_typedef_by_name(dtypedef->get_name());
       if (old_typedef->is_bogus_typedef()) {
@@ -294,7 +295,7 @@ dclass_or_struct:
         ;
 
 dclass:
-        KW_DCLASS optional_name 
+        KW_DCLASS optional_name
 {
   current_class = new DCClass(dc_file, $2, false, false);
 }
@@ -322,7 +323,7 @@ dclass_name:
     if (dclass->is_struct()) {
       yyerror("struct name not allowed");
     }
-  
+
     $$ = dclass;
   }
 }
@@ -398,7 +399,7 @@ dclass_field:
         ;
 
 struct:
-        KW_STRUCT optional_name 
+        KW_STRUCT optional_name
 {
   current_class = new DCClass(dc_file, $2, true, false);
 }
@@ -426,7 +427,7 @@ struct_name:
     if (!dstruct->is_struct()) {
       yyerror("struct name required");
     }
-  
+
     $$ = dstruct;
   }
 }
@@ -538,7 +539,7 @@ unnamed_parameter:
 
 named_parameter_with_default:
         named_parameter
-        | named_parameter '=' 
+        | named_parameter '='
 {
   current_packer = &default_packer;
   current_packer->clear_data();
@@ -553,7 +554,7 @@ named_parameter_with_default:
     is_valid = $1->is_valid();
   }
   if (current_packer->end_pack()) {
-    $1->set_default_value(current_packer->get_string());
+    $1->set_default_value(current_packer->get_bytes());
 
   } else {
     if (is_valid) {
@@ -568,7 +569,7 @@ named_parameter_with_default:
 
 unnamed_parameter_with_default:
         unnamed_parameter
-        | unnamed_parameter '=' 
+        | unnamed_parameter '='
 {
   current_packer = &default_packer;
   current_packer->clear_data();
@@ -583,7 +584,7 @@ unnamed_parameter_with_default:
     is_valid = $1->is_valid();
   }
   if (current_packer->end_pack()) {
-    $1->set_default_value(current_packer->get_string());
+    $1->set_default_value(current_packer->get_bytes());
 
   } else {
     if (is_valid) {
@@ -663,7 +664,7 @@ simple_type_name:
   $$ = simple_param;
 }
         | simple_type_name '%' number
-{ 
+{
   DCSimpleParameter *simple_param = $1->as_simple_parameter();
   nassertr(simple_param != nullptr, 0);
   if (!simple_param->is_numeric_type()) {
@@ -703,10 +704,10 @@ type_name:
           dtypedef = new DCTypedef($1);
         }
       }
-      
+
       dc_file->add_typedef(dtypedef);
     }
-    
+
     $$ = dtypedef->make_new_parameter();
   }
 }
@@ -974,7 +975,7 @@ parameter_value:
 {
   if ($1 != current_packer->get_current_field_name()) {
     ostringstream strm;
-    strm << "Got '" << $1 << "', expected '" 
+    strm << "Got '" << $1 << "', expected '"
          << current_packer->get_current_field_name() << "'";
     yyerror(strm.str());
   }
@@ -1005,7 +1006,7 @@ parameter_actual_value:
 {
   current_packer->pack_literal_value($1);
 }
-        | '{' 
+        | '{'
 {
   current_packer->push();
 }
@@ -1013,7 +1014,7 @@ parameter_actual_value:
 {
   current_packer->pop();
 }
-        | '[' 
+        | '['
 {
   current_packer->push();
 }
@@ -1021,7 +1022,7 @@ parameter_actual_value:
 {
   current_packer->pop();
 }
-        | '(' 
+        | '('
 {
   current_packer->push();
 }
@@ -1223,7 +1224,7 @@ molecular_atom_list:
   if ($3 != nullptr) {
     current_molecular->add_atomic($3);
     if (!$3->is_bogus_field() && !current_molecular->compare_keywords(*$3)) {
-      yyerror("Mismatched keywords in molecule between " + 
+      yyerror("Mismatched keywords in molecule between " +
               current_molecular->get_atomic(0)->get_name() + " and " +
               $3->get_name());
     }
@@ -1283,7 +1284,7 @@ switch_case:
     yyerror("Invalid value for switch parameter");
     current_switch->add_invalid_case();
   } else {
-    int case_index = current_switch->add_case(current_packer->get_string());
+    int case_index = current_switch->add_case(current_packer->get_bytes());
     if (case_index == -1) {
       yyerror("Duplicate case value");
     }

+ 2 - 0
direct/src/dcparser/dcParserDefs.h

@@ -16,6 +16,7 @@
 
 #include "dcbase.h"
 #include "dcSubatomicType.h"
+#include "vector_uchar.h"
 
 class DCFile;
 class DCClass;
@@ -61,6 +62,7 @@ public:
     const DCKeyword *keyword;
   } u;
   std::string str;
+  vector_uchar bytes;
 };
 
 // The yacc-generated code expects to use the symbol 'YYSTYPE' to refer to the

+ 119 - 0
direct/src/dcparser/dcSimpleParameter.cxx

@@ -1069,6 +1069,52 @@ pack_string(DCPackData &pack_data, const string &value,
   }
 }
 
+/**
+ * Packs the indicated numeric or string value into the stream.
+ */
+void DCSimpleParameter::
+pack_blob(DCPackData &pack_data, const vector_uchar &value,
+          bool &pack_error, bool &range_error) const {
+  size_t blob_size = value.size();
+
+  switch (_type) {
+  case ST_char:
+  case ST_uint8:
+  case ST_int8:
+    if (blob_size == 0) {
+      pack_error = true;
+    } else {
+      if (blob_size != 1) {
+        range_error = true;
+      }
+      _uint_range.validate((unsigned int)value[0], range_error);
+      do_pack_uint8(pack_data.get_write_pointer(1), (unsigned int)value[0]);
+    }
+    break;
+
+  case ST_string:
+  case ST_blob:
+    _uint_range.validate(blob_size, range_error);
+    validate_uint_limits(blob_size, 16, range_error);
+    if (_num_length_bytes != 0) {
+      do_pack_uint16(pack_data.get_write_pointer(2), blob_size);
+    }
+    pack_data.append_data((const char *)value.data(), blob_size);
+    break;
+
+  case ST_blob32:
+    _uint_range.validate(blob_size, range_error);
+    if (_num_length_bytes != 0) {
+      do_pack_uint32(pack_data.get_write_pointer(4), blob_size);
+    }
+    pack_data.append_data((const char *)value.data(), blob_size);
+    break;
+
+  default:
+    pack_error = true;
+  }
+}
+
 /**
  * Packs the simpleParameter's specified default value (or a sensible default
  * if no value is specified) into the stream.  Returns true if the default
@@ -1937,6 +1983,79 @@ unpack_string(const char *data, size_t length, size_t &p, string &value,
   return;
 }
 
+/**
+ * Unpacks the current numeric or string value from the stream.
+ */
+void DCSimpleParameter::
+unpack_blob(const char *data, size_t length, size_t &p, vector_uchar &value,
+            bool &pack_error, bool &range_error) const {
+  // If the type is a single byte, unpack it into a string of length 1.
+  switch (_type) {
+  case ST_char:
+  case ST_int8:
+  case ST_uint8:
+    {
+      if (p + 1 > length) {
+        pack_error = true;
+        return;
+      }
+      unsigned int int_value = do_unpack_uint8(data + p);
+      _uint_range.validate(int_value, range_error);
+      value.resize(1);
+      value[0] = int_value;
+      p++;
+    }
+    return;
+
+  default:
+    break;
+  }
+
+  size_t blob_size;
+
+  if (_num_length_bytes == 0) {
+    blob_size = _fixed_byte_size;
+
+  } else {
+    switch (_type) {
+    case ST_string:
+    case ST_blob:
+      if (p + 2 > length) {
+        pack_error = true;
+        return;
+      }
+      blob_size = do_unpack_uint16(data + p);
+      p += 2;
+      break;
+
+    case ST_blob32:
+      if (p + 4 > length) {
+        pack_error = true;
+        return;
+      }
+      blob_size = do_unpack_uint32(data + p);
+      p += 4;
+      break;
+
+    default:
+      pack_error = true;
+      return;
+    }
+  }
+
+  _uint_range.validate(blob_size, range_error);
+
+  if (p + blob_size > length) {
+    pack_error = true;
+    return;
+  }
+  value = vector_uchar((const unsigned char *)data + p,
+                       (const unsigned char *)data + p + blob_size);
+  p += blob_size;
+
+  return;
+}
+
 /**
  * Internally unpacks the current numeric or string value and validates it
  * against the type range limits, but does not return the value.  Returns true

+ 4 - 0
direct/src/dcparser/dcSimpleParameter.h

@@ -62,6 +62,8 @@ public:
                            bool &pack_error, bool &range_error) const;
   virtual void pack_string(DCPackData &pack_data, const std::string &value,
                            bool &pack_error, bool &range_error) const;
+  virtual void pack_blob(DCPackData &pack_data, const vector_uchar &value,
+                         bool &pack_error, bool &range_error) const;
   virtual bool pack_default_value(DCPackData &pack_data, bool &pack_error) const;
 
   virtual void unpack_double(const char *data, size_t length, size_t &p,
@@ -76,6 +78,8 @@ public:
                              uint64_t &value, bool &pack_error, bool &range_error) const;
   virtual void unpack_string(const char *data, size_t length, size_t &p,
                              std::string &value, bool &pack_error, bool &range_error) const;
+  virtual void unpack_blob(const char *data, size_t length, size_t &p,
+                           vector_uchar &value, bool &pack_error, bool &range_error) const;
   virtual bool unpack_validate(const char *data, size_t length, size_t &p,
                                bool &pack_error, bool &range_error) const;
   virtual bool unpack_skip(const char *data, size_t length, size_t &p,

+ 8 - 7
direct/src/dcparser/dcSwitch.cxx

@@ -109,7 +109,7 @@ get_num_cases() const {
  * if no case has this value.
  */
 int DCSwitch::
-get_case_by_value(const string &case_value) const {
+get_case_by_value(const vector_uchar &case_value) const {
   CasesByValue::const_iterator vi;
   vi = _cases_by_value.find(case_value);
   if (vi != _cases_by_value.end()) {
@@ -140,9 +140,9 @@ get_default_case() const {
 /**
  * Returns the packed value associated with the indicated case.
  */
-string DCSwitch::
+vector_uchar DCSwitch::
 get_value(int case_index) const {
-  nassertr(case_index >= 0 && case_index < (int)_cases.size(), string());
+  nassertr(case_index >= 0 && case_index < (int)_cases.size(), vector_uchar());
   return _cases[case_index]->_value;
 }
 
@@ -198,7 +198,7 @@ is_field_valid() const {
  * -1. This is normally called only by the parser.
  */
 int DCSwitch::
-add_case(const string &value) {
+add_case(const vector_uchar &value) {
   int case_index = (int)_cases.size();
   if (!_cases_by_value.insert(CasesByValue::value_type(value, case_index)).second) {
     add_invalid_case();
@@ -283,7 +283,8 @@ add_break() {
 const DCPackerInterface *DCSwitch::
 apply_switch(const char *value_data, size_t length) const {
   CasesByValue::const_iterator vi;
-  vi = _cases_by_value.find(string(value_data, length));
+  vi = _cases_by_value.find(vector_uchar((const unsigned char *)value_data,
+                                         (const unsigned char *)value_data + length));
   if (vi != _cases_by_value.end()) {
     return _cases[(*vi).second]->_fields;
   }
@@ -421,7 +422,7 @@ generate_hash(HashGenerator &hashgen) const {
   Cases::const_iterator ci;
   for (ci = _cases.begin(); ci != _cases.end(); ++ci) {
     const SwitchCase *dcase = (*ci);
-    hashgen.add_string(dcase->_value);
+    hashgen.add_blob(dcase->_value);
 
     const SwitchFields *fields = dcase->_fields;
     hashgen.add_int(fields->_fields.size());
@@ -702,7 +703,7 @@ do_check_match(const DCPackerInterface *) const {
  *
  */
 DCSwitch::SwitchCase::
-SwitchCase(const string &value, DCSwitch::SwitchFields *fields) :
+SwitchCase(const vector_uchar &value, DCSwitch::SwitchFields *fields) :
   _value(value),
   _fields(fields)
 {

+ 6 - 6
direct/src/dcparser/dcSwitch.h

@@ -40,18 +40,18 @@ PUBLISHED:
   DCField *get_key_parameter() const;
 
   int get_num_cases() const;
-  int get_case_by_value(const std::string &case_value) const;
+  int get_case_by_value(const vector_uchar &case_value) const;
   DCPackerInterface *get_case(int n) const;
   DCPackerInterface *get_default_case() const;
 
-  std::string get_value(int case_index) const;
+  vector_uchar get_value(int case_index) const;
   int get_num_fields(int case_index) const;
   DCField *get_field(int case_index, int n) const;
   DCField *get_field_by_name(int case_index, const std::string &name) const;
 
 public:
   bool is_field_valid() const;
-  int add_case(const std::string &value);
+  int add_case(const vector_uchar &value);
   void add_invalid_case();
   bool add_default();
   bool add_field(DCField *field);
@@ -98,13 +98,13 @@ public:
 
   class SwitchCase {
   public:
-    SwitchCase(const std::string &value, SwitchFields *fields);
+    SwitchCase(const vector_uchar &value, SwitchFields *fields);
     ~SwitchCase();
 
     bool do_check_match_switch_case(const SwitchCase *other) const;
 
   public:
-    std::string _value;
+    vector_uchar _value;
     SwitchFields *_fields;
   };
 
@@ -137,7 +137,7 @@ private:
   bool _fields_added;
 
   // This map indexes into the _cases vector, above.
-  typedef pmap<std::string, int> CasesByValue;
+  typedef pmap<vector_uchar, int> CasesByValue;
   CasesByValue _cases_by_value;
 };
 

+ 12 - 0
direct/src/dcparser/hashGenerator.cxx

@@ -56,6 +56,18 @@ add_string(const std::string &str) {
   }
 }
 
+/**
+ * Adds a blob to the hash, by breaking it down into a sequence of integers.
+ */
+void HashGenerator::
+add_blob(const vector_uchar &bytes) {
+  add_int(bytes.size());
+  vector_uchar::const_iterator bi;
+  for (bi = bytes.begin(); bi != bytes.end(); ++bi) {
+    add_int(*bi);
+  }
+}
+
 /**
  * Returns the hash number generated.
  */

+ 2 - 0
direct/src/dcparser/hashGenerator.h

@@ -16,6 +16,7 @@
 
 #include "dcbase.h"
 #include "primeNumberGenerator.h"
+#include "vector_uchar.h"
 
 /**
  * This class generates an arbitrary hash number from a sequence of ints.
@@ -26,6 +27,7 @@ public:
 
   void add_int(int num);
   void add_string(const std::string &str);
+  void add_blob(const vector_uchar &bytes);
 
   unsigned long get_hash() const;
 

+ 16 - 2
direct/src/directscripts/Doxyfile.cxx

@@ -797,7 +797,15 @@ EXCLUDE                = dtool/src/parser-inc \
                          panda/src/linmath/fltnames.h \
                          panda/src/linmath/dblnames.h \
                          panda/src/linmath/dbl2fltnames.h \
-                         panda/src/linmath/flt2dblnames.h
+                         panda/src/linmath/flt2dblnames.h \
+                         panda/src/android \
+                         panda/src/iphone \
+                         panda/src/tinydisplay \
+                         panda/src/movies/dr_flac.h \
+                         dtool/src/dtoolbase/pdtoa.cxx \
+                         dtool/src/dtoolutil/panda_getopt_long.h \
+                         dtool/src/dtoolutil/panda_getopt_impl.h \
+                         dtool/src/dtoolutil/panda_getopt_impl.cxx
 
 # The EXCLUDE_SYMLINKS tag can be used to select whether or not files or
 # directories that are symbolic links (a Unix file system feature) are excluded
@@ -1972,7 +1980,13 @@ PREDEFINED             = TVOLATILE= \
                          EXTEND= \
                          ALLOC_DELETED_CHAIN(x)= \
                          BLOCKING= \
-                         TYPENAME=typename
+                         TYPENAME=typename \
+                         MAKE_PROPERTY(x)= \
+                         MAKE_PROPERTY2(x)= \
+                         MAKE_SEQ(x)= \
+                         MAKE_SEQ_PROPERTY(x)= \
+                         MAKE_MAP_PROPERTY(x)= \
+                         MAKE_MAP_KEYS_SEQ(x)=
 
 # If the MACRO_EXPANSION and EXPAND_ONLY_PREDEF tags are set to YES then this
 # tag can be used to specify a list of macro names that should be expanded. The

+ 3 - 1
direct/src/directscripts/Doxyfile.python

@@ -665,7 +665,9 @@ EXCLUDE_SYMLINKS       = NO
 # for example use the pattern */test/*
 
 EXCLUDE_PATTERNS       = */Opt*-*/* \
-                         */CVS/*
+                         */CVS/* \
+                         */.git/* \
+                         */__pycache__/*
 
 # The EXCLUDE_SYMBOLS tag can be used to specify one or more symbol names 
 # (namespaces, classes, functions, etc.) that should be excluded from the 

+ 79 - 51
direct/src/directscripts/extract_docs.py

@@ -9,9 +9,20 @@ from __future__ import print_function
 
 __all__ = []
 
-import os
+import os, sys
+from distutils import sysconfig
 import panda3d, pandac
-from panda3d.dtoolconfig import *
+from panda3d.interrogatedb import *
+
+
+if 'interrogate_element_is_sequence' not in globals():
+    def interrogate_element_is_sequence(element):
+        return False
+
+if 'interrogate_element_is_mapping' not in globals():
+    def interrogate_element_is_mapping(element):
+        return False
+
 
 LICENSE = """PANDA 3D SOFTWARE
 Copyright (c) Carnegie Mellon University.  All rights reserved.
@@ -19,6 +30,16 @@ All use of this software is subject to the terms of the revised BSD
 license.  You should have received a copy of this license along
 with this source code in a file named \"LICENSE.\"""".split("\n")
 
+MAINPAGE = """@mainpage Panda3D Python API Reference
+Welcome to the Panda3D API reference.
+
+Use the links at the top of this page to browse through the list of modules or
+the list of classes.
+
+This reference is automatically generated from comments in the source code.
+"""
+
+
 def comment(code):
     if not code:
         return ""
@@ -45,50 +66,17 @@ def comment(code):
         return ''
 
 def block_comment(code):
-    if not code:
-        return ""
+    code = code.strip()
 
-    lines = code.split("\n")
-    newlines = []
-    indent = 0
-    reading_desc = False
-
-    for line in lines:
-        if line.startswith("////"):
-            continue
-
-        line = line.rstrip()
-        strline = line.lstrip('/ \t')
-
-        if ':' in strline:
-            pre, post = strline.split(':', 1)
-            pre = pre.rstrip()
-            if pre == "Description":
-                strline = post.lstrip()
-            elif pre in ("Class", "Access", "Function", "Created by", "Enum"):
-                continue
-
-        if strline or len(newlines) > 0:
-            newlines.append('/// ' + strline)
-
-        #if reading_desc:
-        #    newlines.append('/// ' + line[min(indent, len(line) - len(strline)):])
-        #else:
-        #    # A "Description:" text starts the description.
-        #    if strline.startswith("Description"):
-        #        strline = strline[11:].lstrip(': \t')
-        #        indent = len(line) - len(strline)
-        #        reading_desc = True
-        #        newlines.append('/// ' + strline)
-        #    else:
-        #        print line
-
-    newcode = '\n'.join(newlines)
-    if len(newcode) > 0:
-        return newcode
-    else:
+    if not code.startswith('///<') and '@verbatim' not in code:
+        code = code.replace('<', '\\<').replace('>', '\\>')
+
+    if not code or code[0] != '/':
+        # Not really a comment; get rid of it.
         return ""
 
+    return code
+
 def translateFunctionName(name):
     if name.startswith("__"):
         return name
@@ -139,11 +127,17 @@ def translated_type_name(type, scoped=True):
         return "object"
     elif typename == "PN_stdfloat":
         return "float"
+    elif typename == "size_t":
+        return "int"
 
     if interrogate_type_is_atomic(type):
         token = interrogate_type_atomic_token(type)
         if token == 7:
             return 'str'
+        elif token == 8:
+            return 'long'
+        elif token == 9:
+            return 'NoneType'
         else:
             return typename
 
@@ -156,12 +150,25 @@ def translated_type_name(type, scoped=True):
     else:
         return typename
 
+
 def processElement(handle, element):
     if interrogate_element_has_comment(element):
         print(comment(interrogate_element_comment(element)), file=handle)
+    elif interrogate_element_has_getter(element):
+        # If the property has no comment, use the comment of the getter.
+        getter = interrogate_element_getter(element)
+        if interrogate_function_has_comment(getter):
+            print(block_comment(interrogate_function_comment(getter)), file=handle)
+
+    if interrogate_element_is_mapping(element) or \
+       interrogate_element_is_sequence(element):
+        suffix = "[]"
+    else:
+        suffix = ""
 
     print(translated_type_name(interrogate_element_type(element)), end=' ', file=handle)
-    print(interrogate_element_name(element) + ';', file=handle)
+    print(interrogate_element_name(element) + suffix + ';', file=handle)
+
 
 def processFunction(handle, function, isConstructor = False):
     for i_wrapper in range(interrogate_function_number_of_python_wrappers(function)):
@@ -195,6 +202,7 @@ def processFunction(handle, function, isConstructor = False):
 
         print(");", file=handle)
 
+
 def processType(handle, type):
     typename = translated_type_name(type, scoped=False)
     derivations = [ translated_type_name(interrogate_type_get_derivation(type, n)) for n in range(interrogate_type_number_of_derivations(type)) ]
@@ -211,8 +219,10 @@ def processType(handle, type):
             print(interrogate_type_enum_value_name(type, i_value), "=", interrogate_type_enum_value(type, i_value), ",", file=handle)
 
     elif interrogate_type_is_typedef(type):
-        wrapped_type = translated_type_name(interrogate_type_wrapped_type(type))
-        print("typedef %s %s;" % (wrapped_type, typename), file=handle)
+        wrapped_type = interrogate_type_wrapped_type(type)
+        if interrogate_type_is_global(wrapped_type):
+            wrapped_type_name = translated_type_name(wrapped_type)
+            print("typedef %s %s;" % (wrapped_type_name, typename), file=handle)
         return
     else:
         if interrogate_type_is_struct(type):
@@ -249,6 +259,7 @@ def processType(handle, type):
     print("};", file=handle)
 
 def processModule(handle, package):
+    print("Processing module %s" % (package))
     print("namespace %s {" % package, file=handle)
 
     if package != "core":
@@ -280,22 +291,39 @@ def processModule(handle, package):
 if __name__ == "__main__":
     handle = open("pandadoc.hpp", "w")
 
+    mainpage = MAINPAGE.strip()
+    if mainpage:
+        print("/**\n * " + mainpage.replace('\n', '\n * ') + '\n */', file=handle)
+
     print(comment("Panda3D modules that are implemented in C++."), file=handle)
     print("namespace panda3d {", file=handle)
 
     # Determine the path to the interrogatedb files
-    interrogate_add_search_directory(os.path.join(os.path.dirname(pandac.__file__), "..", "..", "etc"))
-    interrogate_add_search_directory(os.path.join(os.path.dirname(pandac.__file__), "input"))
+    pandac = os.path.dirname(pandac.__file__)
+    interrogate_add_search_directory(os.path.join(pandac, "..", "..", "etc"))
+    interrogate_add_search_directory(os.path.join(pandac, "input"))
 
     import panda3d.core
     processModule(handle, "core")
 
+    # Determine the suffix for the extension modules.
+    if sys.version_info >= (3, 0):
+        import _imp
+        ext_suffix = _imp.extension_suffixes()[0]
+    elif sys.platform == "win32":
+        ext_suffix = ".pyd"
+    else:
+        ext_suffix = ".so"
+
     for lib in os.listdir(os.path.dirname(panda3d.__file__)):
-        if lib.endswith(('.pyd', '.so')) and not lib.startswith('core.'):
-            module_name = os.path.splitext(lib)[0]
+        if lib.endswith(ext_suffix) and not lib.startswith('core.'):
+            module_name = lib[:-len(ext_suffix)]
             __import__("panda3d." + module_name)
             processModule(handle, module_name)
 
-
     print("}", file=handle)
     handle.close()
+
+    print("Wrote output to pandadoc.hpp.  You can now run:")
+    print()
+    print("  doxygen built/direct/directscripts/Doxyfile.python")

+ 885 - 108
direct/src/showutil/FreezeTool.py → direct/src/dist/FreezeTool.py

@@ -7,8 +7,12 @@ import os
 import marshal
 import imp
 import platform
-from io import StringIO
+import struct
+import io
 import distutils.sysconfig as sysconf
+import zipfile
+
+from . import pefile
 
 # Temporary (?) try..except to protect against unbuilt p3extend_frozen.
 try:
@@ -28,11 +32,17 @@ isDebugBuild = (python.lower().endswith('_d'))
 
 # These are modules that Python always tries to import up-front.  They
 # must be frozen in any main.exe.
+# NB. if encodings are removed, be sure to remove them from the shortcut in
+# deploy-stub.c.
 startupModules = [
-    'encodings.cp1252', 'encodings.latin_1', 'encodings.utf_8',
-    ]
+    'imp', 'encodings', 'encodings.*',
+]
 if sys.version_info >= (3, 0):
+    # Modules specific to Python 3
     startupModules += ['io', 'marshal', 'importlib.machinery', 'importlib.util']
+else:
+    # Modules specific to Python 2
+    startupModules += []
 
 # These are some special init functions for some built-in Python modules that
 # deviate from the standard naming convention.  A value of None means that a
@@ -42,10 +52,72 @@ builtinInitFuncs = {
     '__builtin__': None,
     'sys': None,
     'exceptions': None,
-    '_imp': 'PyInit_imp',
     '_warnings': '_PyWarnings_Init',
     'marshal': 'PyMarshal_Init',
 }
+if sys.version_info < (3, 7):
+    builtinInitFuncs['_imp'] = 'PyInit_imp'
+
+# These are modules that are not found normally for these modules. Add them
+# to an include list so users do not have to do this manually.
+try:
+    from pytest import freeze_includes as pytest_imports
+except ImportError:
+    def pytest_imports():
+        return []
+
+hiddenImports = {
+    'pytest': pytest_imports(),
+    'pkg_resources': [
+        'pkg_resources.*.*',
+    ],
+    'xml.etree.cElementTree': ['xml.etree.ElementTree'],
+    'datetime': ['_strptime'],
+    'keyring.backends': ['keyring.backends.*'],
+    'matplotlib.font_manager': ['encodings.mac_roman'],
+    'direct.particles': ['direct.particles.ParticleManagerGlobal'],
+    'numpy.core._multiarray_umath': [
+        'numpy.core._internal',
+        'numpy.core._dtype_ctypes',
+        'numpy.core._methods',
+    ],
+}
+
+if sys.version_info >= (3,):
+    hiddenImports['matplotlib.backends._backend_tk'] = ['tkinter']
+else:
+    hiddenImports['matplotlib.backends._backend_tk'] = ['Tkinter']
+
+
+# These are overrides for specific modules.
+overrideModules = {
+    # Used by the warnings module, among others, to get line numbers.  Since
+    # we set __file__, this would cause it to try and extract Python code
+    # lines from the main executable, which we don't want.
+    'linecache': """__all__ = ["getline", "clearcache", "checkcache"]
+
+cache = {}
+
+def getline(filename, lineno, module_globals=None):
+    return ''
+
+def clearcache():
+    global cache
+    cache = {}
+
+def getlines(filename, module_globals=None):
+    return []
+
+def checkcache(filename=None):
+    pass
+
+def updatecache(filename, module_globals=None):
+    pass
+
+def lazycache(filename, module_globals):
+    pass
+""",
+}
 
 # These are missing modules that we've reported already this session.
 reportedMissing = {}
@@ -250,13 +322,17 @@ frozenMainCode = """
 
 #if PY_MAJOR_VERSION >= 3
 #include <locale.h>
+
+#if PY_MINOR_VERSION < 5
+#define Py_DecodeLocale _Py_char2wchar
+#endif
 #endif
 
 #ifdef MS_WINDOWS
 extern void PyWinFreeze_ExeInit(void);
 extern void PyWinFreeze_ExeTerm(void);
 
-extern DL_IMPORT(int) PyImport_ExtendInittab(struct _inittab *newtab);
+extern PyAPI_FUNC(int) PyImport_ExtendInittab(struct _inittab *newtab);
 #endif
 
 /* Main program */
@@ -271,18 +347,14 @@ Py_FrozenMain(int argc, char **argv)
 
 #if PY_MAJOR_VERSION >= 3
     int i;
-    char *oldloc = NULL;
+    char *oldloc;
     wchar_t **argv_copy = NULL;
     /* We need a second copies, as Python might modify the first one. */
     wchar_t **argv_copy2 = NULL;
 
     if (argc > 0) {
-        argv_copy = PyMem_RawMalloc(sizeof(wchar_t*) * argc);
-        argv_copy2 = PyMem_RawMalloc(sizeof(wchar_t*) * argc);
-        if (!argv_copy || !argv_copy2) {
-            fprintf(stderr, \"out of memory\\n\");
-            goto error;
-        }
+        argv_copy = (wchar_t **)alloca(sizeof(wchar_t *) * argc);
+        argv_copy2 = (wchar_t **)alloca(sizeof(wchar_t *) * argc);
     }
 #endif
 
@@ -302,12 +374,7 @@ Py_FrozenMain(int argc, char **argv)
     }
 
 #if PY_MAJOR_VERSION >= 3
-    oldloc = _PyMem_RawStrdup(setlocale(LC_ALL, NULL));
-    if (!oldloc) {
-        fprintf(stderr, \"out of memory\\n\");
-        goto error;
-    }
-
+    oldloc = setlocale(LC_ALL, NULL);
     setlocale(LC_ALL, \"\");
     for (i = 0; i < argc; i++) {
         argv_copy[i] = Py_DecodeLocale(argv[i], NULL);
@@ -320,8 +387,6 @@ Py_FrozenMain(int argc, char **argv)
         }
     }
     setlocale(LC_ALL, oldloc);
-    PyMem_RawFree(oldloc);
-    oldloc = NULL;
 #endif
 
 #ifdef MS_WINDOWS
@@ -371,13 +436,15 @@ Py_FrozenMain(int argc, char **argv)
 
 #if PY_MAJOR_VERSION >= 3
 error:
-    PyMem_RawFree(argv_copy);
     if (argv_copy2) {
-        for (i = 0; i < argc; i++)
+        for (i = 0; i < argc; i++) {
+#if PY_MINOR_VERSION >= 4
             PyMem_RawFree(argv_copy2[i]);
-        PyMem_RawFree(argv_copy2);
+#else
+            PyMem_Free(argv_copy2[i]);
+#endif
+        }
     }
-    PyMem_RawFree(oldloc);
 #endif
     return sts;
 }
@@ -649,7 +716,7 @@ class Freezer:
             return 'ModuleDef(%s)' % (', '.join(args))
 
     def __init__(self, previous = None, debugLevel = 0,
-                 platform = None):
+                 platform = None, path=None):
         # Normally, we are freezing for our own platform.  Change this
         # if untrue.
         self.platform = platform or PandaSystem.getPlatform()
@@ -660,6 +727,10 @@ class Freezer:
         # default object will be created when it is needed.
         self.cenv = None
 
+        # This is the search path to use for Python modules.  Leave it
+        # to the default value of None to use sys.path.
+        self.path = path
+
         # The filename extension to append to the source file before
         # compiling.
         self.sourceExtension = '.c'
@@ -715,10 +786,46 @@ class Freezer:
         # special path mangling.)
         for moduleName, module in list(sys.modules.items()):
             if module and hasattr(module, '__path__'):
-                path = getattr(module, '__path__')
+                path = list(getattr(module, '__path__'))
                 if path:
                     modulefinder.AddPackagePath(moduleName, path[0])
 
+        # Suffix/extension for Python C extension modules
+        if self.platform == PandaSystem.getPlatform():
+            self.moduleSuffixes = imp.get_suffixes()
+
+            # Set extension for Python files to binary mode
+            for i, suffix in enumerate(self.moduleSuffixes):
+                if suffix[2] == imp.PY_SOURCE:
+                    self.moduleSuffixes[i] = (suffix[0], 'rb', imp.PY_SOURCE)
+        else:
+            self.moduleSuffixes = [('.py', 'rb', 1), ('.pyc', 'rb', 2)]
+            if 'linux' in self.platform:
+                self.moduleSuffixes += [
+                    ('.cpython-{0}{1}m-x86_64-linux-gnu.so'.format(*sys.version_info), 'rb', 3),
+                    ('.cpython-{0}{1}m-i686-linux-gnu.so'.format(*sys.version_info), 'rb', 3),
+                    ('.abi{0}.so'.format(sys.version_info[0]), 'rb', 3),
+                    ('.so', 'rb', 3),
+                ]
+            elif 'win' in self.platform:
+                self.moduleSuffixes += [
+                    ('.cp{0}{1}-win_amd64.pyd'.format(*sys.version_info), 'rb', 3),
+                    ('.cp{0}{1}-win32.pyd'.format(*sys.version_info), 'rb', 3),
+                    ('.pyd', 'rb', 3),
+                ]
+            elif 'mac' in self.platform:
+                self.moduleSuffixes += [
+                    ('.cpython-{0}{1}m-darwin.so'.format(*sys.version_info), 'rb', 3),
+                    ('.abi{0}.so'.format(sys.version_info[0]), 'rb', 3),
+                    ('.so', 'rb', 3),
+                ]
+            else: # FreeBSD et al.
+                self.moduleSuffixes += [
+                    ('.cpython-{0}{1}m.so'.format(*sys.version_info), 'rb', 3),
+                    ('.abi{0}.so'.format(*sys.version_info), 'rb', 3),
+                    ('.so', 'rb', 3),
+                ]
+
     def excludeFrom(self, freezer):
         """ Excludes all modules that have already been processed by
         the indicated FreezeTool.  This is equivalent to passing the
@@ -843,6 +950,60 @@ class Freezer:
 
         return modules
 
+    def _gatherSubmodules(self, moduleName, implicit = False, newName = None,
+                          filename = None, guess = False, fromSource = None,
+                          text = None):
+        if not newName:
+            newName = moduleName
+
+        assert(moduleName.endswith('.*'))
+        assert(newName.endswith('.*'))
+
+        mdefs = {}
+
+        # Find the parent module, so we can get its directory.
+        parentName = moduleName[:-2]
+        newParentName = newName[:-2]
+        parentNames = [(parentName, newParentName)]
+
+        if parentName.endswith('.*'):
+            assert(newParentName.endswith('.*'))
+            # Another special case.  The parent name "*" means to
+            # return all possible directories within a particular
+            # directory.
+
+            topName = parentName[:-2]
+            newTopName = newParentName[:-2]
+            parentNames = []
+            modulePath = self.getModulePath(topName)
+            if modulePath:
+                for dirname in modulePath:
+                    for basename in os.listdir(dirname):
+                        if os.path.exists(os.path.join(dirname, basename, '__init__.py')):
+                            parentName = '%s.%s' % (topName, basename)
+                            newParentName = '%s.%s' % (newTopName, basename)
+                            if self.getModulePath(parentName):
+                                parentNames.append((parentName, newParentName))
+
+        for parentName, newParentName in parentNames:
+            modules = self.getModuleStar(parentName)
+
+            if modules == None:
+                # It's actually a regular module.
+                mdef[newParentName] = self.ModuleDef(
+                    parentName, implicit = implicit, guess = guess,
+                    fromSource = fromSource, text = text)
+
+            else:
+                # Now get all the py files in the parent directory.
+                for basename in modules:
+                    moduleName = '%s.%s' % (parentName, basename)
+                    newName = '%s.%s' % (newParentName, basename)
+                    mdefs[newName] = self.ModuleDef(
+                        moduleName, implicit = implicit, guess = True,
+                        fromSource = fromSource)
+        return mdefs
+
     def addModule(self, moduleName, implicit = False, newName = None,
                   filename = None, guess = False, fromSource = None,
                   text = None):
@@ -869,49 +1030,9 @@ class Freezer:
             newName = moduleName
 
         if moduleName.endswith('.*'):
-            assert(newName.endswith('.*'))
-            # Find the parent module, so we can get its directory.
-            parentName = moduleName[:-2]
-            newParentName = newName[:-2]
-            parentNames = [(parentName, newParentName)]
-
-            if parentName.endswith('.*'):
-                assert(newParentName.endswith('.*'))
-                # Another special case.  The parent name "*" means to
-                # return all possible directories within a particular
-                # directory.
-
-                topName = parentName[:-2]
-                newTopName = newParentName[:-2]
-                parentNames = []
-                modulePath = self.getModulePath(topName)
-                if modulePath:
-                    for dirname in modulePath:
-                        for basename in os.listdir(dirname):
-                            if os.path.exists(os.path.join(dirname, basename, '__init__.py')):
-                                parentName = '%s.%s' % (topName, basename)
-                                newParentName = '%s.%s' % (newTopName, basename)
-                                if self.getModulePath(parentName):
-                                    parentNames.append((parentName, newParentName))
-
-            for parentName, newParentName in parentNames:
-                modules = self.getModuleStar(parentName)
-
-                if modules == None:
-                    # It's actually a regular module.
-                    self.modules[newParentName] = self.ModuleDef(
-                        parentName, implicit = implicit, guess = guess,
-                        fromSource = fromSource, text = text)
-
-                else:
-                    # Now get all the py files in the parent directory.
-                    for basename in modules:
-                        moduleName = '%s.%s' % (parentName, basename)
-                        newName = '%s.%s' % (newParentName, basename)
-                        mdef = self.ModuleDef(
-                            moduleName, implicit = implicit, guess = True,
-                            fromSource = fromSource)
-                        self.modules[newName] = mdef
+            self.modules.update(self._gatherSubmodules(
+                moduleName, implicit, newName, filename,
+                guess, fromSource, text))
         else:
             # A normal, explicit module name.
             self.modules[newName] = self.ModuleDef(
@@ -935,7 +1056,7 @@ class Freezer:
 
             for moduleName in startupModules:
                 if moduleName not in self.modules:
-                    self.modules[moduleName] = self.ModuleDef(moduleName, implicit = True)
+                    self.addModule(moduleName, implicit = True)
 
         # Excluding a parent module also excludes all its
         # (non-explicit) children, unless the parent has allowChildren
@@ -968,7 +1089,7 @@ class Freezer:
             else:
                 includes.append(mdef)
 
-        self.mf = PandaModuleFinder(excludes = list(excludeDict.keys()))
+        self.mf = PandaModuleFinder(excludes=list(excludeDict.keys()), suffixes=self.moduleSuffixes, path=self.path)
 
         # Attempt to import the explicit modules into the modulefinder.
 
@@ -1001,6 +1122,20 @@ class Freezer:
                 # module.
                 pass
 
+        # Check if any new modules we found have "hidden" imports
+        for origName in list(self.mf.modules.keys()):
+            hidden = hiddenImports.get(origName, [])
+            for modname in hidden:
+                if modname.endswith('.*'):
+                    mdefs = self._gatherSubmodules(modname, implicit = True)
+                    for mdef in mdefs.values():
+                        try:
+                            self.__loadModule(mdef)
+                        except ImportError:
+                            pass
+                else:
+                    self.__loadModule(self.ModuleDef(modname, implicit = True))
+
         # Now, any new modules we found get added to the export list.
         for origName in list(self.mf.modules.keys()):
             if origName not in origToNewName:
@@ -1073,11 +1208,11 @@ class Freezer:
                 stuff = ("", "rb", imp.PY_COMPILED)
                 self.mf.load_module(mdef.moduleName, fp, pathname, stuff)
             else:
+                stuff = ("", "rb", imp.PY_SOURCE)
                 if mdef.text:
-                    fp = StringIO(mdef.text)
+                    fp = io.StringIO(mdef.text)
                 else:
-                    fp = open(pathname, 'U')
-                stuff = ("", "r", imp.PY_SOURCE)
+                    fp = open(pathname, 'rb')
                 self.mf.load_module(mdef.moduleName, fp, pathname, stuff)
 
             if tempPath:
@@ -1394,7 +1529,7 @@ class Freezer:
                     libName = module.split('.')[-1]
                     initFunc = builtinInitFuncs.get(module, 'PyInit_' + libName)
                     if initFunc:
-                        text += 'extern DL_IMPORT(PyObject) *%s(void);\n' % (initFunc)
+                        text += 'extern PyAPI_FUNC(PyObject) *%s(void);\n' % (initFunc)
             text += '\n'
 
             if sys.platform == "win32":
@@ -1417,7 +1552,7 @@ class Freezer:
                     libName = module.split('.')[-1]
                     initFunc = builtinInitFuncs.get(module, 'init' + libName)
                     if initFunc:
-                        text += 'extern DL_IMPORT(void) %s(void);\n' % (initFunc)
+                        text += 'extern PyAPI_FUNC(void) %s(void);\n' % (initFunc)
             text += '\n'
 
             if sys.platform == "win32":
@@ -1549,6 +1684,498 @@ class Freezer:
 
         return target
 
+    def generateRuntimeFromStub(self, target, stub_file, use_console, fields={},
+                                log_append=False):
+        # We must have a __main__ module to make an exe file.
+        if not self.__writingModule('__main__'):
+            message = "Can't generate an executable without a __main__ module."
+            raise Exception(message)
+
+        if self.platform.startswith('win'):
+            modext = '.pyd'
+        else:
+            modext = '.so'
+
+        # First gather up the strings and code for all the module names, and
+        # put those in a string pool.
+        pool = b""
+        strings = set()
+
+        for moduleName, mdef in self.getModuleDefs():
+            strings.add(moduleName.encode('ascii'))
+
+        for value in fields.values():
+            if value is not None:
+                strings.add(value.encode('utf-8'))
+
+        # Sort by length descending, allowing reuse of partial strings.
+        strings = sorted(strings, key=lambda str:-len(str))
+        string_offsets = {}
+
+        # Now add the strings to the pool, and collect the offsets relative to
+        # the beginning of the pool.
+        for string in strings:
+            # First check whether it's already in there; it could be part of
+            # a longer string.
+            offset = pool.find(string + b'\0')
+            if offset < 0:
+                offset = len(pool)
+                pool += string + b'\0'
+            string_offsets[string] = offset
+
+        # Now go through the modules and add them to the pool as well.  These
+        # are not 0-terminated, but we later record their sizes and names in
+        # a table after the blob header.
+        moduleList = []
+
+        for moduleName, mdef in self.getModuleDefs():
+            origName = mdef.moduleName
+            if mdef.forbid:
+                # Explicitly disallow importing this module.
+                moduleList.append((moduleName, 0, 0))
+                continue
+
+            # For whatever it's worth, align the code blocks.
+            if len(pool) & 3 != 0:
+                pad = (4 - (len(pool) & 3))
+                pool += b'\0' * pad
+
+            assert not mdef.exclude
+            # Allow importing this module.
+            module = self.mf.modules.get(origName, None)
+            code = getattr(module, "__code__", None)
+            if code:
+                code = marshal.dumps(code)
+                size = len(code)
+                if getattr(module, "__path__", None):
+                    # Indicate package by negative size
+                    size = -size
+                moduleList.append((moduleName, len(pool), size))
+                pool += code
+                continue
+
+            # This is a module with no associated Python code.  It is either
+            # an extension module or a builtin module.  Get the filename, if
+            # it is the former.
+            extensionFilename = getattr(module, '__file__', None)
+
+            if extensionFilename:
+                self.extras.append((moduleName, extensionFilename))
+
+            # If it is a submodule of a frozen module, Python will have
+            # trouble importing it as a builtin module.  Synthesize a frozen
+            # module that loads it dynamically.
+            if '.' in moduleName:
+                if self.platform.startswith("macosx") and not use_console:
+                    # We write the Frameworks directory to sys.path[0].
+                    code = 'import sys;del sys.modules["%s"];import sys,os,imp;imp.load_dynamic("%s",os.path.join(sys.path[0], "%s%s"))' % (moduleName, moduleName, moduleName, modext)
+                else:
+                    code = 'import sys;del sys.modules["%s"];import sys,os,imp;imp.load_dynamic("%s",os.path.join(os.path.dirname(sys.executable), "%s%s"))' % (moduleName, moduleName, moduleName, modext)
+                if sys.version_info >= (3, 2):
+                    code = compile(code, moduleName, 'exec', optimize=2)
+                else:
+                    code = compile(code, moduleName, 'exec')
+                code = marshal.dumps(code)
+                moduleList.append((moduleName, len(pool), len(code)))
+                pool += code
+
+        # Determine the format of the header and module list entries depending
+        # on the platform.
+        num_pointers = 12
+        stub_data = bytearray(stub_file.read())
+        bitnesses = self._get_executable_bitnesses(stub_data)
+
+        header_layouts = {
+            32: '<QQHHHH8x%dII' % num_pointers,
+            64: '<QQHHHH8x%dQQ' % num_pointers,
+        }
+        entry_layouts = {
+            32: '<IIi',
+            64: '<QQixxxx',
+        }
+
+        # Calculate the size of the module tables, so that we can determine
+        # the proper offset for the string pointers.  There can be more than
+        # one module table for macOS executables.  Sort the bitnesses so that
+        # the alignment is correct.
+        bitnesses = sorted(bitnesses, reverse=True)
+
+        pool_offset = 0
+        for bitness in bitnesses:
+            pool_offset += (len(moduleList) + 1) * struct.calcsize(entry_layouts[bitness])
+
+        # Now we can determine the offset of the blob.
+        if self.platform.startswith('win'):
+            # We don't use mmap on Windows.  Align just for good measure.
+            blob_align = 32
+        else:
+            # Align to page size, so that it can be mmapped.
+            blob_align = 4096
+
+        # Add padding before the blob if necessary.
+        blob_offset = len(stub_data)
+        if (blob_offset & (blob_align - 1)) != 0:
+            pad = (blob_align - (blob_offset & (blob_align - 1)))
+            stub_data += (b'\0' * pad)
+            blob_offset += pad
+        assert (blob_offset % blob_align) == 0
+        assert blob_offset == len(stub_data)
+
+        # Also determine the total blob size now.  Add padding to the end.
+        blob_size = pool_offset + len(pool)
+        if blob_size & 31 != 0:
+            pad = (32 - (blob_size & 31))
+            blob_size += pad
+
+        # Calculate the offsets for the variables.  These are pointers,
+        # relative to the beginning of the blob.
+        field_offsets = {}
+        for key, value in fields.items():
+            if value is not None:
+                encoded = value.encode('utf-8')
+                field_offsets[key] = pool_offset + string_offsets[encoded]
+
+        # OK, now go and write the blob.  This consists of the module table
+        # (there may be two in the case of a macOS universal (fat) binary).
+        blob = b""
+        append_offset = False
+        for bitness in bitnesses:
+            entry_layout = entry_layouts[bitness]
+            header_layout = header_layouts[bitness]
+
+            table_offset = len(blob)
+            for moduleName, offset, size in moduleList:
+                encoded = moduleName.encode('ascii')
+                string_offset = pool_offset + string_offsets[encoded]
+                if size != 0:
+                    offset += pool_offset
+                blob += struct.pack(entry_layout, string_offset, offset, size)
+
+            # A null entry marks the end of the module table.
+            blob += struct.pack(entry_layout, 0, 0, 0)
+
+            flags = 0
+            if log_append:
+                flags |= 1
+
+            # Compose the header we will be writing to the stub, to tell it
+            # where to find the module data blob, as well as other variables.
+            header = struct.pack(header_layout,
+                blob_offset,
+                blob_size,
+                1, # Version number
+                num_pointers, # Number of pointers that follow
+                0, # Codepage, not yet used
+                flags,
+                table_offset, # Module table pointer.
+                # The following variables need to be set before static init
+                # time.  See configPageManager.cxx, where they are read.
+                field_offsets.get('prc_data', 0),
+                field_offsets.get('default_prc_dir', 0),
+                field_offsets.get('prc_dir_envvars', 0),
+                field_offsets.get('prc_path_envvars', 0),
+                field_offsets.get('prc_patterns', 0),
+                field_offsets.get('prc_encrypted_patterns', 0),
+                field_offsets.get('prc_encryption_key', 0),
+                field_offsets.get('prc_executable_patterns', 0),
+                field_offsets.get('prc_executable_args_envvar', 0),
+                field_offsets.get('main_dir', 0),
+                field_offsets.get('log_filename', 0),
+                0)
+
+            # Now, find the location of the 'blobinfo' symbol in the binary,
+            # to which we will write our header.
+            if not self._replace_symbol(stub_data, b'blobinfo', header, bitness=bitness):
+                # This must be a legacy deploy-stub, which requires the offset to
+                # be appended to the end.
+                append_offset = True
+
+        # Add the string/code pool.
+        assert len(blob) == pool_offset
+        blob += pool
+        del pool
+
+        # Now pad out the blob to the calculated blob size.
+        if len(blob) < blob_size:
+            blob += b'\0' * (blob_size - len(blob))
+        assert len(blob) == blob_size
+
+        if append_offset:
+            # This is for legacy deploy-stub.
+            print("WARNING: Could not find blob header. Is deploy-stub outdated?")
+            blob += struct.pack('<Q', blob_offset)
+
+        with open(target, 'wb') as f:
+            f.write(stub_data)
+            assert f.tell() == blob_offset
+            f.write(blob)
+
+        os.chmod(target, 0o755)
+        return target
+
+    def _get_executable_bitnesses(self, data):
+        """Returns the bitnesses (32 or 64) of the given executable data.
+        This will contain 1 element for non-fat executables."""
+
+        if data.startswith(b'MZ'):
+            # A Windows PE file.
+            offset, = struct.unpack_from('<I', data, 0x3c)
+            assert data[offset:offset+4] == b'PE\0\0'
+
+            magic, = struct.unpack_from('<H', data, offset + 24)
+            assert magic in (0x010b, 0x020b)
+            if magic == 0x020b:
+                return (64,)
+            else:
+                return (32,)
+
+        elif data.startswith(b"\177ELF"):
+            # A Linux/FreeBSD ELF executable.
+            elfclass = ord(data[4:5])
+            assert elfclass in (1, 2)
+            return (elfclass * 32,)
+
+        elif data[:4] in (b'\xFE\xED\xFA\xCE', b'\xCE\xFA\xED\xFE'):
+            # 32-bit Mach-O file, as used on macOS.
+            return (32,)
+
+        elif data[:4] in (b'\xFE\xED\xFA\xCF', b'\xCF\xFA\xED\xFE'):
+            # 64-bit Mach-O file, as used on macOS.
+            return (64,)
+
+        elif data[:4] in (b'\xCA\xFE\xBA\xBE', b'\xBE\xBA\xFE\xCA'):
+            # Universal binary with 32-bit offsets.
+            num_fat, = struct.unpack_from('>I', data, 4)
+            bitnesses = set()
+            ptr = 8
+            for i in range(num_fat):
+                cputype, cpusubtype, offset, size, align = \
+                    struct.unpack_from('>IIIII', data, ptr)
+                ptr += 20
+
+                if (cputype & 0x1000000) != 0:
+                    bitnesses.add(64)
+                else:
+                    bitnesses.add(32)
+            return tuple(bitnesses)
+
+        elif data[:4] in (b'\xCA\xFE\xBA\xBF', b'\xBF\xBA\xFE\xCA'):
+            # Universal binary with 64-bit offsets.
+            num_fat, = struct.unpack_from('>I', data, 4)
+            bitnesses = set()
+            ptr = 8
+            for i in range(num_fat):
+                cputype, cpusubtype, offset, size, align = \
+                    struct.unpack_from('>QQQQQ', data, ptr)
+                ptr += 40
+
+                if (cputype & 0x1000000) != 0:
+                    bitnesses.add(64)
+                else:
+                    bitnesses.add(32)
+            return tuple(bitnesses)
+
+    def _replace_symbol(self, data, symbol_name, replacement, bitness=None):
+        """We store a custom section in the binary file containing a header
+        containing offsets to the binary data.
+        If bitness is set, and the binary in question is a macOS universal
+        binary, it only replaces for binaries with the given bitness. """
+
+        if data.startswith(b'MZ'):
+            # A Windows PE file.
+            pe = pefile.PEFile()
+            pe.read(io.BytesIO(data))
+            addr = pe.get_export_address(symbol_name)
+            if addr is not None:
+                # We found it, return its offset in the file.
+                offset = pe.get_address_offset(addr)
+                if offset is not None:
+                    data[offset:offset+len(replacement)] = replacement
+                    return True
+
+        elif data.startswith(b"\177ELF"):
+            return self._replace_symbol_elf(data, symbol_name, replacement)
+
+        elif data[:4] in (b'\xFE\xED\xFA\xCE', b'\xCE\xFA\xED\xFE',
+                          b'\xFE\xED\xFA\xCF', b'\xCF\xFA\xED\xFE'):
+            off = self._find_symbol_macho(data, symbol_name)
+            if off is not None:
+                data[off:off+len(replacement)] = replacement
+                return True
+            return False
+
+        elif data[:4] in (b'\xCA\xFE\xBA\xBE', b'\xBE\xBA\xFE\xCA'):
+            # Universal binary with 32-bit offsets.
+            num_fat, = struct.unpack_from('>I', data, 4)
+            replaced = False
+            ptr = 8
+            for i in range(num_fat):
+                cputype, cpusubtype, offset, size, align = \
+                    struct.unpack_from('>IIIII', data, ptr)
+                ptr += 20
+
+                # Does this match the requested bitness?
+                if bitness is not None and ((cputype & 0x1000000) != 0) != (bitness == 64):
+                    continue
+
+                macho_data = data[offset:offset+size]
+                off = self._find_symbol_macho(macho_data, symbol_name)
+                if off is not None:
+                    off += offset
+                    data[off:off+len(replacement)] = replacement
+                    replaced = True
+
+            return replaced
+
+        elif data[:4] in (b'\xCA\xFE\xBA\xBF', b'\xBF\xBA\xFE\xCA'):
+            # Universal binary with 64-bit offsets.
+            num_fat, = struct.unpack_from('>I', data, 4)
+            replaced = False
+            ptr = 8
+            for i in range(num_fat):
+                cputype, cpusubtype, offset, size, align = \
+                    struct.unpack_from('>QQQQQ', data, ptr)
+                ptr += 40
+
+                # Does this match the requested bitness?
+                if bitness is not None and ((cputype & 0x1000000) != 0) != (bitness == 64):
+                    continue
+
+                macho_data = data[offset:offset+size]
+                off = self._find_symbol_macho(macho_data, symbol_name)
+                if off is not None:
+                    off += offset
+                    data[off:off+len(replacement)] = replacement
+                    replaced = True
+
+            return replaced
+
+        # We don't know what kind of file this is.
+        return False
+
+    def _replace_symbol_elf(self, elf_data, symbol_name, replacement):
+        """ The Linux/FreeBSD implementation of _replace_symbol. """
+
+        replaced = False
+
+        # Make sure we read in the correct endianness and integer size
+        endian = "<>"[ord(elf_data[5:6]) - 1]
+        is_64bit = ord(elf_data[4:5]) - 1 # 0 = 32-bits, 1 = 64-bits
+        header_struct = endian + ("HHIIIIIHHHHHH", "HHIQQQIHHHHHH")[is_64bit]
+        section_struct = endian + ("4xI4xIIII8xI", "4xI8xQQQI12xQ")[is_64bit]
+        symbol_struct = endian + ("IIIBBH", "IBBHQQ")[is_64bit]
+
+        header_size = struct.calcsize(header_struct)
+        type, machine, version, entry, phoff, shoff, flags, ehsize, phentsize, phnum, shentsize, shnum, shstrndx \
+          = struct.unpack_from(header_struct, elf_data, 16)
+        section_offsets = []
+        symbol_tables = []
+        string_tables = {}
+
+        # Seek to the section header table and find the symbol tables.
+        ptr = shoff
+        for i in range(shnum):
+            type, addr, offset, size, link, entsize = struct.unpack_from(section_struct, elf_data[ptr:ptr+shentsize])
+            ptr += shentsize
+            section_offsets.append(offset - addr)
+            if type == 0x0B and link != 0: # SHT_DYNSYM, links to string table
+                symbol_tables.append((offset, size, link, entsize))
+                string_tables[link] = None
+
+        # Read the relevant string tables.
+        for idx in list(string_tables.keys()):
+            ptr = shoff + idx * shentsize
+            type, addr, offset, size, link, entsize = struct.unpack_from(section_struct, elf_data[ptr:ptr+shentsize])
+            if type == 3:
+                string_tables[idx] = elf_data[offset:offset+size]
+
+        # Loop through to find the offset of the "blobinfo" symbol.
+        for offset, size, link, entsize in symbol_tables:
+            entries = size // entsize
+            for i in range(entries):
+                ptr = offset + i * entsize
+                fields = struct.unpack_from(symbol_struct, elf_data[ptr:ptr+entsize])
+                if is_64bit:
+                    name, info, other, shndx, value, size = fields
+                else:
+                    name, value, size, info, other, shndx = fields
+
+                if not name:
+                    continue
+
+                name = string_tables[link][name : string_tables[link].find(b'\0', name)]
+                if name == symbol_name:
+                    if shndx == 0: # SHN_UNDEF
+                        continue
+                    elif shndx >= 0xff00 and shndx <= 0xffff:
+                        assert False
+                    else:
+                        # Got it.  Make the replacement.
+                        off = section_offsets[shndx] + value
+                        elf_data[off:off+len(replacement)] = replacement
+                        replaced = True
+
+        return replaced
+
+    def _find_symbol_macho(self, macho_data, symbol_name):
+        """ Returns the offset of the given symbol in the binary file. """
+
+        if macho_data[:4] in (b'\xCE\xFA\xED\xFE', b'\xCF\xFA\xED\xFE'):
+            endian = '<'
+        else:
+            endian = '>'
+
+        cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags = \
+            struct.unpack_from(endian + 'IIIIII', macho_data, 4)
+
+        is_64bit = (cputype & 0x1000000) != 0
+        segments = []
+
+        cmd_ptr = 28
+        nlist_struct = endian + 'IBBHI'
+        if is_64bit:
+            nlist_struct = endian + 'IBBHQ'
+            cmd_ptr += 4
+        nlist_size = struct.calcsize(nlist_struct)
+
+        for i in range(ncmds):
+            cmd, cmd_size = struct.unpack_from(endian + 'II', macho_data, cmd_ptr)
+            cmd_data = macho_data[cmd_ptr+8:cmd_ptr+cmd_size]
+            cmd_ptr += cmd_size
+
+            cmd &= ~0x80000000
+
+            if cmd == 0x01: # LC_SEGMENT
+                segname, vmaddr, vmsize, fileoff, filesize, maxprot, initprot, nsects, flags = \
+                    struct.unpack_from(endian + '16sIIIIIIII', cmd_data)
+                segments.append((vmaddr, vmsize, fileoff))
+
+            elif cmd == 0x19: # LC_SEGMENT_64
+                segname, vmaddr, vmsize, fileoff, filesize, maxprot, initprot, nsects, flags = \
+                    struct.unpack_from(endian + '16sQQQQIIII', cmd_data)
+                segments.append((vmaddr, vmsize, fileoff))
+
+            elif cmd == 0x2: # LC_SYMTAB
+                symoff, nsyms, stroff, strsize = \
+                    struct.unpack_from(endian + 'IIII', cmd_data)
+
+                strings = macho_data[stroff:stroff+strsize]
+
+                for i in range(nsyms):
+                    strx, type, sect, desc, value = struct.unpack_from(nlist_struct, macho_data, symoff)
+                    symoff += nlist_size
+                    name = strings[strx : strings.find(b'\0', strx)]
+
+                    if name == b'_' + symbol_name:
+                        # Find out in which segment this is.
+                        for vmaddr, vmsize, fileoff in segments:
+                            # Is it defined in this segment?
+                            rel = value - vmaddr
+                            if rel >= 0 and rel < vmsize:
+                                # Yes, so return the symbol offset.
+                                return fileoff + rel
+                        print("Could not find memory address for symbol %s" % (symbol_name))
+
     def makeModuleDef(self, mangledName, code):
         result = ''
         result += 'static unsigned char %s[] = {' % (mangledName)
@@ -1588,51 +2215,201 @@ class Freezer:
         return True
 
 class PandaModuleFinder(modulefinder.ModuleFinder):
-    """ We subclass ModuleFinder here, to add functionality for
-    finding the libpandaexpress etc. modules that interrogate
-    produces. """
 
     def __init__(self, *args, **kw):
+        """
+        :param path: search path to look on, defaults to sys.path
+        :param suffixes: defaults to imp.get_suffixes()
+        :param excludes: a list of modules to exclude
+        :param debug: an integer indicating the level of verbosity
+        """
+
+        self.suffixes = kw.pop('suffixes', imp.get_suffixes())
+
         modulefinder.ModuleFinder.__init__(self, *args, **kw)
 
-    def find_module(self, name, path, *args, **kwargs):
-        if imp.is_frozen(name):
-            # Don't pick up modules that are frozen into p3dpython.
-            raise ImportError("'%s' is a frozen module" % (name))
+        # Make sure we don't open a .whl/.zip file more than once.
+        self._zip_files = {}
 
-        try:
-            return modulefinder.ModuleFinder.find_module(self, name, path, *args, **kwargs)
-        except ImportError:
-            # It wasn't found through the normal channels.  Maybe it's
-            # one of ours, or maybe it's frozen?
-            if path:
-                # Only if we're not looking on a particular path,
-                # though.
-                raise
+    def _open_file(self, path, mode):
+        """ Opens a module at the given path, which may contain a zip file.
+        Returns None if the module could not be found. """
 
-            if p3extend_frozen and p3extend_frozen.is_frozen_module(name):
-                # It's a frozen module.
-                return (None, name, ('', '', imp.PY_FROZEN))
+        if os.path.isfile(path):
+            if 'b' not in mode:
+                return io.open(path, mode, encoding='utf8')
+            else:
+                return open(path, mode)
+
+        # Is there a zip file along the path?
+        dir, dirname = os.path.split(path)
+        fn = dirname
+        while dirname:
+            if os.path.isfile(dir):
+                # Okay, this is actually a file.  Is it a zip file?
+                if dir in self._zip_files:
+                    # Yes, and we've previously opened this.
+                    zip = self._zip_files[dir]
+                elif zipfile.is_zipfile(dir):
+                    zip = zipfile.ZipFile(dir)
+                    self._zip_files[dir] = zip
+                else:
+                    # It's a different kind of file.  Stop looking.
+                    return None
+
+                try:
+                    fp = zip.open(fn.replace(os.path.sep, '/'), 'r')
+                except KeyError:
+                    return None
+
+                if sys.version_info >= (3, 0) and 'b' not in mode:
+                    return io.TextIOWrapper(fp, encoding='utf8')
+                return fp
 
-        message = "DLL loader cannot find %s." % (name)
-        raise ImportError(message)
+            # Look at the parent directory.
+            dir, dirname = os.path.split(dir)
+            fn = os.path.join(dirname, fn)
+
+        return None
 
     def load_module(self, fqname, fp, pathname, file_info):
+        """Copied from ModuleFinder.load_module with fixes to handle sending bytes
+        to compile() for PY_SOURCE types. Sending bytes to compile allows it to
+        handle file encodings."""
+
         suffix, mode, type = file_info
+        self.msgin(2, "load_module", fqname, fp and "fp", pathname)
+        if type == imp.PKG_DIRECTORY:
+            m = self.load_package(fqname, pathname)
+            self.msgout(2, "load_module ->", m)
+            return m
 
-        if type == imp.PY_FROZEN:
-            # It's a frozen module.
-            co, isPackage = p3extend_frozen.get_frozen_module_code(pathname)
-            m = self.add_module(fqname)
-            m.__file__ = '<frozen>'
-            if isPackage:
-                m.__path__ = [pathname]
-            co = marshal.loads(co)
+        if type == imp.PY_SOURCE:
+            if fqname in overrideModules:
+                # This module has a custom override.
+                code = overrideModules[fqname]
+            else:
+                code = fp.read()
+
+            code += b'\n' if isinstance(code, bytes) else '\n'
+            co = compile(code, pathname, 'exec')
+        elif type == imp.PY_COMPILED:
+            try:
+                marshal_data = importlib._bootstrap_external._validate_bytecode_header(fp.read())
+            except ImportError as exc:
+                self.msgout(2, "raise ImportError: " + str(exc), pathname)
+                raise
+            co = marshal.loads(marshal_data)
+        else:
+            co = None
+
+        m = self.add_module(fqname)
+        m.__file__ = pathname
+        if co:
             if self.replace_paths:
                 co = self.replace_paths_in_code(co)
             m.__code__ = co
             self.scan_code(co, m)
-            self.msgout(2, "load_module ->", m)
-            return m
+        self.msgout(2, "load_module ->", m)
+        return m
+
+    # This function is provided here since the Python library version has a bug
+    # (see bpo-35376)
+    def _safe_import_hook(self, name, caller, fromlist, level=-1):
+        # wrapper for self.import_hook() that won't raise ImportError
+        if name in self.badmodules:
+            self._add_badmodule(name, caller)
+            return
+        try:
+            self.import_hook(name, caller, level=level)
+        except ImportError as msg:
+            self.msg(2, "ImportError:", str(msg))
+            self._add_badmodule(name, caller)
+        else:
+            if fromlist:
+                for sub in fromlist:
+                    fullname = name + "." + sub
+                    if fullname in self.badmodules:
+                        self._add_badmodule(fullname, caller)
+                        continue
+                    try:
+                        self.import_hook(name, caller, [sub], level=level)
+                    except ImportError as msg:
+                        self.msg(2, "ImportError:", str(msg))
+                        self._add_badmodule(fullname, caller)
+
+    def find_module(self, name, path=None, parent=None):
+        """ Finds a module with the indicated name on the given search path
+        (or self.path if None).  Returns a tuple like (fp, path, stuff), where
+        stuff is a tuple like (suffix, mode, type). """
+
+        if imp.is_frozen(name):
+            # Don't pick up modules that are frozen into p3dpython.
+            raise ImportError("'%s' is a frozen module" % (name))
 
-        return modulefinder.ModuleFinder.load_module(self, fqname, fp, pathname, (suffix, mode, type))
+        if parent is not None:
+            fullname = parent.__name__+'.'+name
+        else:
+            fullname = name
+        if fullname in self.excludes:
+            raise ImportError(name)
+
+        # If we have a custom override for this module, we know we have it.
+        if fullname in overrideModules:
+            return (None, '', ('.py', 'r', imp.PY_SOURCE))
+
+        # If no search path is given, look for a built-in module.
+        if path is None:
+            if name in sys.builtin_module_names:
+                return (None, None, ('', '', imp.C_BUILTIN))
+
+            path = self.path
+
+        # Look for the module on the search path.
+        for dir_path in path:
+            basename = os.path.join(dir_path, name.split('.')[-1])
+
+            # Look for recognized extensions.
+            for stuff in self.suffixes:
+                suffix, mode, _ = stuff
+                fp = self._open_file(basename + suffix, mode)
+                if fp:
+                    return (fp, basename + suffix, stuff)
+
+            # Consider a package, i.e. a directory containing __init__.py.
+            for suffix, mode, _ in self.suffixes:
+                init = os.path.join(basename, '__init__' + suffix)
+                if self._open_file(init, mode):
+                    return (None, basename, ('', '', imp.PKG_DIRECTORY))
+
+        # It wasn't found through the normal channels.  Maybe it's one of
+        # ours, or maybe it's frozen?
+        if not path:
+            # Only if we're not looking on a particular path, though.
+            if p3extend_frozen and p3extend_frozen.is_frozen_module(name):
+                # It's a frozen module.
+                return (None, name, ('', '', imp.PY_FROZEN))
+
+        raise ImportError(name)
+
+    def find_all_submodules(self, m):
+        # Overridden so that we can define our own suffixes.
+        if not m.__path__:
+            return
+        modules = {}
+        for dir in m.__path__:
+            try:
+                names = os.listdir(dir)
+            except OSError:
+                self.msg(2, "can't list directory", dir)
+                continue
+            for name in names:
+                mod = None
+                for suff in self.suffixes:
+                    n = len(suff)
+                    if name[-n:] == suff:
+                        mod = name[:-n]
+                        break
+                if mod and mod != "__init__":
+                    modules[mod] = mod
+        return modules.keys()

+ 0 - 0
direct/src/dist/__init__.py


+ 1387 - 0
direct/src/dist/commands.py

@@ -0,0 +1,1387 @@
+from __future__ import print_function
+
+import collections
+import os
+import pip
+import plistlib
+import sys
+import subprocess
+import zipfile
+import re
+import shutil
+import stat
+import struct
+import imp
+import string
+
+import setuptools
+import distutils.log
+
+from . import FreezeTool
+from . import pefile
+import panda3d.core as p3d
+
+
+if 'basestring' not in globals():
+    basestring = str
+
+
+if sys.version_info < (3, 0):
+    # Python 3 defines these subtypes of IOError, but Python 2 doesn't.
+    FileNotFoundError = IOError
+
+
+def _parse_list(input):
+    if isinstance(input, basestring):
+        input = input.strip().replace(',', '\n')
+        if input:
+            return [item.strip() for item in input.split('\n') if item.strip()]
+        else:
+            return []
+    else:
+        return input
+
+
+def _parse_dict(input):
+    if isinstance(input, dict):
+        return input
+    d = {}
+    for item in _parse_list(input):
+        key, sep, value = item.partition('=')
+        d[key.strip()] = value.strip()
+    return d
+
+
+
+def egg2bam(_build_cmd, srcpath, dstpath):
+    dstpath = dstpath + '.bam'
+    try:
+        subprocess.check_call([
+            'egg2bam',
+            '-o', dstpath,
+            '-pd', os.path.dirname(os.path.abspath(srcpath)),
+            '-ps', 'rel',
+            srcpath
+        ])
+    except FileNotFoundError:
+        raise RuntimeError('egg2bam failed: egg2bam was not found in the PATH')
+    except (subprocess.CalledProcessError, OSError) as err:
+        raise RuntimeError('egg2bam failed: {}'.format(err))
+    return dstpath
+
+macosx_binary_magics = (
+    b'\xFE\xED\xFA\xCE', b'\xCE\xFA\xED\xFE',
+    b'\xFE\xED\xFA\xCF', b'\xCF\xFA\xED\xFE',
+    b'\xCA\xFE\xBA\xBE', b'\xBE\xBA\xFE\xCA',
+    b'\xCA\xFE\xBA\xBF', b'\xBF\xBA\xFE\xCA')
+
+# Some dependencies need data directories to be extracted. This dictionary maps
+# modules with data to extract. The values are lists of tuples of the form
+# (source_pattern, destination_pattern, flags). The flags is a set of strings.
+
+PACKAGE_DATA_DIRS = {
+    'matplotlib':  [('matplotlib/mpl-data/*', 'mpl-data', {})],
+    'jsonschema':  [('jsonschema/schemas/*', 'schemas', {})],
+    'cefpython3': [
+        ('cefpython3/*.pak', '', {}),
+        ('cefpython3/*.dat', '', {}),
+        ('cefpython3/*.bin', '', {}),
+        ('cefpython3/*.dll', '', {}),
+        ('cefpython3/libcef.so', '', {}),
+        ('cefpython3/LICENSE.txt', '', {}),
+        ('cefpython3/License', '', {}),
+        ('cefpython3/subprocess*', '', {'PKG_DATA_MAKE_EXECUTABLE'}),
+        ('cefpython3/locals/*', 'locals', {}),
+        ('cefpython3/Chromium Embedded Framework.framework/Resources', 'Chromium Embedded Framework.framework/Resources', {}),
+    ],
+}
+
+# Some dependencies have extra directories that need to be scanned for DLLs.
+# This dictionary maps wheel basenames (ie. the part of the .whl basename
+# before the first hyphen) to a list of directories inside the .whl.
+
+PACKAGE_LIB_DIRS = {
+    'scipy':  ['scipy/extra-dll'],
+}
+
+# site.py for Python 2.
+SITE_PY2 = u"""
+import sys
+
+sys.frozen = True
+
+# Override __import__ to set __file__ for frozen modules.
+prev_import = __import__
+def __import__(*args, **kwargs):
+    mod = prev_import(*args, **kwargs)
+    if mod:
+        mod.__file__ = sys.executable
+    return mod
+
+# Add our custom __import__ version to the global scope, as well as a builtin
+# definition for __file__ so that it is available in the module itself.
+import __builtin__
+__builtin__.__import__ = __import__
+__builtin__.__file__ = sys.executable
+del __builtin__
+
+# Set the TCL_LIBRARY directory to the location of the Tcl/Tk/Tix files.
+import os
+tcl_dir = os.path.join(os.path.dirname(sys.executable), 'tcl')
+if os.path.isdir(tcl_dir):
+    for dir in os.listdir(tcl_dir):
+        sub_dir = os.path.join(tcl_dir, dir)
+        if os.path.isdir(sub_dir):
+            if dir.startswith('tcl'):
+                os.environ['TCL_LIBRARY'] = sub_dir
+            if dir.startswith('tk'):
+                os.environ['TK_LIBRARY'] = sub_dir
+            if dir.startswith('tix'):
+                os.environ['TIX_LIBRARY'] = sub_dir
+del os
+"""
+
+# site.py for Python 3.
+SITE_PY3 = u"""
+import sys
+from _frozen_importlib import _imp, FrozenImporter
+
+sys.frozen = True
+
+if sys.platform == 'win32':
+    # Make sure the preferred encoding is something we actually support.
+    import _bootlocale
+    enc = _bootlocale.getpreferredencoding().lower()
+    if enc != 'utf-8' and not _imp.is_frozen('encodings.%s' % (enc)):
+        def getpreferredencoding(do_setlocale=True):
+            return 'mbcs'
+        _bootlocale.getpreferredencoding = getpreferredencoding
+
+# Alter FrozenImporter to give a __file__ property to frozen modules.
+_find_spec = FrozenImporter.find_spec
+
+def find_spec(fullname, path=None, target=None):
+    spec = _find_spec(fullname, path=path, target=target)
+    if spec:
+        spec.has_location = True
+        spec.origin = sys.executable
+    return spec
+
+def get_data(path):
+    with open(path, 'rb') as fp:
+        return fp.read()
+
+FrozenImporter.find_spec = find_spec
+FrozenImporter.get_data = get_data
+
+# Set the TCL_LIBRARY directory to the location of the Tcl/Tk/Tix files.
+import os
+tcl_dir = os.path.join(os.path.dirname(sys.executable), 'tcl')
+if os.path.isdir(tcl_dir):
+    for dir in os.listdir(tcl_dir):
+        sub_dir = os.path.join(tcl_dir, dir)
+        if os.path.isdir(sub_dir):
+            if dir.startswith('tcl'):
+                os.environ['TCL_LIBRARY'] = sub_dir
+            if dir.startswith('tk'):
+                os.environ['TK_LIBRARY'] = sub_dir
+            if dir.startswith('tix'):
+                os.environ['TIX_LIBRARY'] = sub_dir
+del os
+"""
+
+SITE_PY = SITE_PY3 if sys.version_info >= (3,) else SITE_PY2
+
+
+class build_apps(setuptools.Command):
+    description = 'build Panda3D applications'
+    user_options = [
+        ('build-base=', None, 'directory to build applications in'),
+        ('requirements-path=', None, 'path to requirements.txt file for pip'),
+        ('platforms=', 'p', 'a list of platforms to build for'),
+    ]
+    default_file_handlers = {
+        '.egg': egg2bam,
+    }
+
+    def initialize_options(self):
+        self.build_base = os.path.join(os.getcwd(), 'build')
+        self.gui_apps = {}
+        self.console_apps = {}
+        self.macos_main_app = None
+        self.rename_paths = {}
+        self.include_patterns = []
+        self.exclude_patterns = []
+        self.include_modules = {}
+        self.exclude_modules = {}
+        self.platforms = [
+            'manylinux1_x86_64',
+            'macosx_10_6_x86_64',
+            'win_amd64',
+        ]
+        self.plugins = []
+        self.embed_prc_data = True
+        self.extra_prc_files = []
+        self.extra_prc_data = ''
+        self.default_prc_dir = None
+        self.log_filename = None
+        self.log_append = False
+        self.requirements_path = os.path.join(os.getcwd(), 'requirements.txt')
+        self.use_optimized_wheels = True
+        self.optimized_wheel_index = ''
+        self.pypi_extra_indexes = []
+        self.file_handlers = {}
+        self.exclude_dependencies = [
+            # Windows
+            'kernel32.dll', 'user32.dll', 'wsock32.dll', 'ws2_32.dll',
+            'advapi32.dll', 'opengl32.dll', 'glu32.dll', 'gdi32.dll',
+            'shell32.dll', 'ntdll.dll', 'ws2help.dll', 'rpcrt4.dll',
+            'imm32.dll', 'ddraw.dll', 'shlwapi.dll', 'secur32.dll',
+            'dciman32.dll', 'comdlg32.dll', 'comctl32.dll', 'ole32.dll',
+            'oleaut32.dll', 'gdiplus.dll', 'winmm.dll', 'iphlpapi.dll',
+            'msvcrt.dll', 'kernelbase.dll', 'msimg32.dll', 'msacm32.dll',
+
+            # manylinux1/linux
+            'libdl.so.*', 'libstdc++.so.*', 'libm.so.*', 'libgcc_s.so.*',
+            'libpthread.so.*', 'libc.so.*', 'ld-linux-x86-64.so.*',
+            'libgl.so.*', 'libx11.so.*', 'libreadline.so.*', 'libncursesw.so.*',
+            'libbz2.so.*', 'libz.so.*', 'liblzma.so.*', 'librt.so.*', 'libutil.so.*',
+
+            # macOS
+            '/usr/lib/libstdc++.*.dylib',
+            '/usr/lib/libz.*.dylib',
+            '/usr/lib/libobjc.*.dylib',
+            '/usr/lib/libSystem.*.dylib',
+            '/usr/lib/libbz2.*.dylib',
+            '/usr/lib/libedit.*.dylib',
+            '/System/Library/**',
+        ]
+        self.package_data_dirs = {}
+
+        # We keep track of the zip files we've opened.
+        self._zip_files = {}
+
+    def _get_zip_file(self, path):
+        if path in self._zip_files:
+            return self._zip_files[path]
+
+        zip = zipfile.ZipFile(path)
+        self._zip_files[path] = zip
+        return zip
+
+    def finalize_options(self):
+        # We need to massage the inputs a bit in case they came from a
+        # setup.cfg file.
+        self.gui_apps = _parse_dict(self.gui_apps)
+        self.console_apps = _parse_dict(self.console_apps)
+
+        self.rename_paths = _parse_dict(self.rename_paths)
+        self.include_patterns = _parse_list(self.include_patterns)
+        self.exclude_patterns = _parse_list(self.exclude_patterns)
+        self.include_modules = {
+            key: _parse_list(value)
+            for key, value in _parse_dict(self.include_modules).items()
+        }
+        self.exclude_modules = {
+            key: _parse_list(value)
+            for key, value in _parse_dict(self.exclude_modules).items()
+        }
+        self.platforms = _parse_list(self.platforms)
+        self.plugins = _parse_list(self.plugins)
+        self.extra_prc_files = _parse_list(self.extra_prc_files)
+
+        if self.default_prc_dir is None:
+            self.default_prc_dir = '<auto>etc' if not self.embed_prc_data else ''
+
+        num_gui_apps = len(self.gui_apps)
+        num_console_apps = len(self.console_apps)
+
+        if not self.macos_main_app:
+            if num_gui_apps > 1:
+                assert False, 'macos_main_app must be defined if more than one gui_app is defined'
+            elif num_gui_apps == 1:
+                self.macos_main_app = list(self.gui_apps.keys())[0]
+
+        use_pipenv = (
+            'Pipfile' in os.path.basename(self.requirements_path) or
+            not os.path.exists(self.requirements_path) and os.path.exists('Pipfile')
+        )
+        if use_pipenv:
+            reqspath = os.path.join(self.build_base, 'requirements.txt')
+            with open(reqspath, 'w') as reqsfile:
+                subprocess.check_call(['pipenv', 'lock', '--requirements'], stdout=reqsfile)
+            self.requirements_path = reqspath
+
+        if self.use_optimized_wheels:
+            if not self.optimized_wheel_index:
+                # Try to find an appropriate wheel index
+
+                # Start with the release index
+                self.optimized_wheel_index = 'https://archive.panda3d.org/simple/opt'
+
+                # See if a buildbot build is being used
+                with open(self.requirements_path) as reqsfile:
+                    reqsdata = reqsfile.read()
+                matches = re.search(r'--extra-index-url (https*://archive.panda3d.org/.*\b)', reqsdata)
+                if matches and matches.group(1):
+                    self.optimized_wheel_index = matches.group(1)
+                    if not matches.group(1).endswith('opt'):
+                        self.optimized_wheel_index += '/opt'
+
+            assert self.optimized_wheel_index, 'An index for optimized wheels must be defined if use_optimized_wheels is set'
+
+        assert os.path.exists(self.requirements_path), 'Requirements.txt path does not exist: {}'.format(self.requirements_path)
+        assert num_gui_apps + num_console_apps != 0, 'Must specify at least one app in either gui_apps or console_apps'
+
+        self.exclude_dependencies = [p3d.GlobPattern(i) for i in self.exclude_dependencies]
+        for glob in self.exclude_dependencies:
+            glob.case_sensitive = False
+
+        tmp = self.default_file_handlers.copy()
+        tmp.update(self.file_handlers)
+        self.file_handlers = tmp
+
+        tmp = self.package_data_dirs.copy()
+        tmp.update(self.package_data_dirs)
+        self.package_data_dirs = tmp
+
+    def run(self):
+        self.announce('Building platforms: {0}'.format(','.join(self.platforms)), distutils.log.INFO)
+
+        for platform in self.platforms:
+            self.build_runtimes(platform, True)
+
+    def download_wheels(self, platform):
+        """ Downloads wheels for the given platform using pip. This includes panda3d
+        wheels. These are special wheels that are expected to contain a deploy_libs
+        directory containing the Python runtime libraries, which will be added
+        to sys.path."""
+
+        self.announce('Gathering wheels for platform: {}'.format(platform), distutils.log.INFO)
+
+        whldir = os.path.join(self.build_base, '__whl_cache__')
+
+        #TODO find a better way to get abi tag than from internal/private pip APIs
+        if hasattr(pip, 'pep425tags'):
+            pep425tags = pip.pep425tags
+            wheel = pip.wheel
+        else:
+            from pip._internal import pep425tags, wheel
+
+        abi_tag = pep425tags.get_abi_tag()
+
+        if 'u' in abi_tag and (platform.startswith('win') or platform.startswith('macosx')):
+            abi_tag = abi_tag.replace('u', '')
+
+        # For these distributions, we need to append 'u' on Linux
+        if abi_tag in ('cp26m', 'cp27m', 'cp32m') and not platform.startswith('win') and not platform.startswith('macosx'):
+            abi_tag += 'u'
+
+        pip_version = pip.__version__.split('.')
+        if int(pip_version[0]) < 9:
+            raise RuntimeError("pip 9.0 or greater is required, but found {}".format(pip.__version__))
+
+        # Remove any .zip files. These are built from a VCS and block for an
+        # interactive prompt on subsequent downloads.
+        if os.path.exists(whldir):
+            for whl in os.listdir(whldir):
+                if whl.endswith('.zip'):
+                    os.remove(os.path.join(whldir, whl))
+
+        pip_args = [
+            'download',
+            '-d', whldir,
+            '-r', self.requirements_path,
+            '--only-binary', ':all:',
+            '--platform', platform,
+            '--abi', abi_tag
+        ]
+
+        if self.use_optimized_wheels:
+            pip_args += [
+                '--extra-index-url', self.optimized_wheel_index
+            ]
+
+        for index in self.pypi_extra_indexes:
+            pip_args += ['--extra-index-url', index]
+
+        subprocess.check_call([sys.executable, '-m', 'pip'] + pip_args)
+
+        # Now figure out which of the downloaded wheels are relevant to us.
+        tags = pep425tags.get_supported(platform=platform, abi=abi_tag)
+        wheelpaths = []
+        for filename in os.listdir(whldir):
+            try:
+                whl = wheel.Wheel(filename)
+            except wheel.InvalidWheelFilename:
+                continue
+
+            if whl.supported(tags):
+                wheelpaths.append(os.path.join(whldir, filename))
+
+        return wheelpaths
+
+    def bundle_macos_app(self, builddir):
+        """Bundle built runtime into a .app for macOS"""
+
+        appname = '{}.app'.format(self.macos_main_app)
+        appdir = os.path.join(builddir, appname)
+        contentsdir = os.path.join(appdir, 'Contents')
+        macosdir = os.path.join(contentsdir, 'MacOS')
+        fwdir = os.path.join(contentsdir, 'Frameworks')
+        resdir = os.path.join(contentsdir, 'Resources')
+
+        self.announce('Bundling macOS app into {}'.format(appdir), distutils.log.INFO)
+
+        # Create initial directory structure
+        os.makedirs(macosdir)
+        os.makedirs(fwdir)
+        os.makedirs(resdir)
+
+        # Move files over
+        for fname in os.listdir(builddir):
+            src = os.path.join(builddir, fname)
+            if appdir in src:
+                continue
+
+            if fname in self.gui_apps or self.console_apps:
+                dst = macosdir
+            elif os.path.isfile(src) and open(src, 'rb').read(4) in macosx_binary_magics:
+                dst = fwdir
+            else:
+                dst = resdir
+            shutil.move(src, dst)
+
+        # Write out Info.plist
+        plist = {
+            'CFBundleName': appname,
+            'CFBundleDisplayName': appname, #TODO use name from setup.py/cfg
+            'CFBundleIdentifier': '', #TODO
+            'CFBundleVersion': '0.0.0', #TODO get from setup.py
+            'CFBundlePackageType': 'APPL',
+            'CFBundleSignature': '', #TODO
+            'CFBundleExecutable': self.macos_main_app,
+        }
+        with open(os.path.join(contentsdir, 'Info.plist'), 'wb') as f:
+            if hasattr(plistlib, 'dump'):
+                plistlib.dump(plist, f)
+            else:
+                plistlib.writePlist(plist, f)
+
+
+    def build_runtimes(self, platform, use_wheels):
+        """ Builds the distributions for the given platform. """
+
+        builddir = os.path.join(self.build_base, platform)
+
+        if os.path.exists(builddir):
+            shutil.rmtree(builddir)
+        os.makedirs(builddir)
+
+        path = sys.path[:]
+        p3dwhl = None
+
+        if use_wheels:
+            wheelpaths = self.download_wheels(platform)
+
+            for whl in wheelpaths:
+                if os.path.basename(whl).startswith('panda3d-'):
+                    p3dwhlfn = whl
+                    p3dwhl = self._get_zip_file(p3dwhlfn)
+                    break
+            else:
+                raise RuntimeError("Missing panda3d wheel for platform: {}".format(platform))
+
+            if self.use_optimized_wheels:
+                # Check to see if we have an optimized wheel
+                localtag = p3dwhlfn.split('+')[1].split('-')[0] if '+' in p3dwhlfn else ''
+                if not localtag.endswith('opt'):
+                    self.announce(
+                        'Could not find an optimized wheel (using index {}) for platform: {}'.format(self.optimized_wheel_index, platform),
+                        distutils.log.WARN
+                    )
+
+            #whlfiles = {whl: self._get_zip_file(whl) for whl in wheelpaths}
+
+            # Add whl files to the path so they are picked up by modulefinder
+            for whl in wheelpaths:
+                path.insert(0, whl)
+
+            # Add deploy_libs from panda3d whl to the path
+            path.insert(0, os.path.join(p3dwhlfn, 'deploy_libs'))
+
+
+        self.announce('Building runtime for platform: {}'.format(platform), distutils.log.INFO)
+
+        # Gather PRC data
+        prcstring = ''
+        if not use_wheels:
+            dtool_fn = p3d.Filename(p3d.ExecutionEnvironment.get_dtool_name())
+            libdir = os.path.dirname(dtool_fn.to_os_specific())
+            etcdir = os.path.join(libdir, '..', 'etc')
+
+            for fn in os.listdir(etcdir):
+                if fn.lower().endswith('.prc'):
+                    with open(os.path.join(etcdir, fn)) as f:
+                        prcstring += f.read()
+        else:
+            etcfiles = [i for i in p3dwhl.namelist() if i.endswith('.prc')]
+            for fn in etcfiles:
+                with p3dwhl.open(fn) as f:
+                    prcstring += f.read().decode('utf8')
+        user_prcstring = self.extra_prc_data
+        for fn in self.extra_prc_files:
+            with open(fn) as f:
+                user_prcstring += f.read()
+
+        # Clenup PRC data
+        check_plugins = [
+            #TODO find a better way to get this list
+            'pandaegg',
+            'p3ffmpeg',
+            'p3ptloader',
+        ]
+        def parse_prc(prcstr, warn_on_missing_plugin):
+            out = []
+            for ln in prcstr.split('\n'):
+                ln = ln.strip()
+                useline = True
+                if ln.startswith('#') or not ln:
+                    continue
+                if 'model-cache-dir' in ln:
+                    ln = ln.replace('/panda3d', '/{}'.format(self.distribution.get_name()))
+                for plugin in check_plugins:
+                    if plugin in ln and plugin not in self.plugins:
+                        useline = False
+                        if warn_on_missing_plugin:
+                            self.warn(
+                                "Missing plugin ({0}) referenced in user PRC data".format(plugin)
+                            )
+                        break
+                if useline:
+                    out.append(ln)
+            return out
+        prcexport = parse_prc(prcstring, 0) + parse_prc(user_prcstring, 1)
+
+        # Export PRC data
+        prcexport = '\n'.join(prcexport)
+        if not self.embed_prc_data:
+            prcdir = self.default_prc_dir.replace('<auto>', '')
+            prcdir = os.path.join(builddir, prcdir)
+            os.makedirs(prcdir)
+            with open (os.path.join(prcdir, '00-panda3d.prc'), 'w') as f:
+                f.write(prcexport)
+
+        # Create runtimes
+        freezer_extras = set()
+        freezer_modules = set()
+        freezer_modpaths = set()
+        ext_suffixes = set()
+        def create_runtime(appname, mainscript, use_console):
+            freezer = FreezeTool.Freezer(platform=platform, path=path)
+            freezer.addModule('__main__', filename=mainscript)
+            freezer.addModule('site', filename='site.py', text=SITE_PY)
+            for incmod in self.include_modules.get(appname, []) + self.include_modules.get('*', []):
+                freezer.addModule(incmod)
+            for exmod in self.exclude_modules.get(appname, []) + self.exclude_modules.get('*', []):
+                freezer.excludeModule(exmod)
+            freezer.done(addStartupModules=True)
+
+            target_path = os.path.join(builddir, appname)
+
+            stub_name = 'deploy-stub'
+            if platform.startswith('win') or 'macosx' in platform:
+                if not use_console:
+                    stub_name = 'deploy-stubw'
+
+            if platform.startswith('win'):
+                stub_name += '.exe'
+                target_path += '.exe'
+
+            if use_wheels:
+                stub_file = p3dwhl.open('panda3d_tools/{0}'.format(stub_name))
+            else:
+                dtool_path = p3d.Filename(p3d.ExecutionEnvironment.get_dtool_name()).to_os_specific()
+                stub_path = os.path.join(os.path.dirname(dtool_path), '..', 'bin', stub_name)
+                stub_file = open(stub_path, 'rb')
+
+            freezer.generateRuntimeFromStub(target_path, stub_file, use_console, {
+                'prc_data': prcexport if self.embed_prc_data else None,
+                'default_prc_dir': self.default_prc_dir,
+                'prc_dir_envvars': None,
+                'prc_path_envvars': None,
+                'prc_patterns': None,
+                'prc_encrypted_patterns': None,
+                'prc_encryption_key': None,
+                'prc_executable_patterns': None,
+                'prc_executable_args_envvar': None,
+                'main_dir': None,
+                'log_filename': self.expand_path(self.log_filename, platform),
+            }, self.log_append)
+            stub_file.close()
+
+            # Copy the dependencies.
+            search_path = [builddir]
+            if use_wheels:
+                search_path.append(os.path.join(p3dwhlfn, 'deploy_libs'))
+            self.copy_dependencies(target_path, builddir, search_path, stub_name)
+
+            freezer_extras.update(freezer.extras)
+            freezer_modules.update(freezer.getAllModuleNames())
+            freezer_modpaths.update({
+                mod[1].filename.to_os_specific()
+                for mod in freezer.getModuleDefs() if mod[1].filename
+            })
+            for suffix in freezer.moduleSuffixes:
+                if suffix[2] == imp.C_EXTENSION:
+                    ext_suffixes.add(suffix[0])
+
+        for appname, scriptname in self.gui_apps.items():
+            create_runtime(appname, scriptname, False)
+
+        for appname, scriptname in self.console_apps.items():
+            create_runtime(appname, scriptname, True)
+
+        # Copy extension modules
+        whl_modules = []
+        whl_modules_ext = ''
+        if use_wheels:
+            # Get the module libs
+            whl_modules = []
+
+            for i in p3dwhl.namelist():
+                if not i.startswith('deploy_libs/'):
+                    continue
+
+                if not any(i.endswith(suffix) for suffix in ext_suffixes):
+                    continue
+
+                base = os.path.basename(i)
+                module, _, ext = base.partition('.')
+                whl_modules.append(module)
+                whl_modules_ext = ext
+
+        # Make sure to copy any builtins that have shared objects in the
+        # deploy libs, assuming they are not already in freezer_extras.
+        for mod, source_path in freezer_extras:
+            freezer_modules.discard(mod)
+
+        for mod in freezer_modules:
+            if mod in whl_modules:
+                freezer_extras.add((mod, None))
+
+        # Copy over necessary plugins
+        plugin_list = ['panda3d/lib{}'.format(i) for i in self.plugins]
+        for lib in p3dwhl.namelist():
+            plugname = lib.split('.', 1)[0]
+            if plugname in plugin_list:
+                source_path = os.path.join(p3dwhlfn, lib)
+                target_path = os.path.join(builddir, os.path.basename(lib))
+                search_path = [os.path.dirname(source_path)]
+                self.copy_with_dependencies(source_path, target_path, search_path)
+
+        # Copy any shared objects we need
+        for module, source_path in freezer_extras:
+            if source_path is not None:
+                # Rename panda3d/core.pyd to panda3d.core.pyd
+                basename = os.path.basename(source_path)
+                if '.' in module:
+                    basename = module.rsplit('.', 1)[0] + '.' + basename
+
+                # Remove python version string
+                if sys.version_info >= (3, 0):
+                    parts = basename.split('.')
+                    if len(parts) >= 3 and '-' in parts[-2]:
+                        parts = parts[:-2] + parts[-1:]
+                        basename = '.'.join(parts)
+            else:
+                # Builtin module, but might not be builtin in wheel libs, so double check
+                if module in whl_modules:
+                    source_path = os.path.join(p3dwhlfn, 'deploy_libs/{}.{}'.format(module, whl_modules_ext))#'{0}/deploy_libs/{1}.{2}'.format(p3dwhlfn, module, whl_modules_ext)
+                    basename = os.path.basename(source_path)
+                    #XXX should we remove python version string here too?
+                else:
+                    continue
+
+            # If this is a dynamic library, search for dependencies.
+            search_path = [os.path.dirname(source_path)]
+            if use_wheels:
+                search_path.append(os.path.join(p3dwhlfn, 'deploy_libs'))
+
+                # If the .whl containing this file has a .libs directory, add
+                # it to the path.  This is an auditwheel/numpy convention.
+                if '.whl' + os.sep in source_path:
+                    whl, wf = source_path.split('.whl' + os.path.sep)
+                    whl += '.whl'
+                    rootdir = wf.split(os.path.sep, 1)[0]
+                    search_path.append(os.path.join(whl, rootdir, '.libs'))
+
+                    # Also look for more specific per-package cases, defined in
+                    # PACKAGE_LIB_DIRS at the top of this file.
+                    whl_name = os.path.basename(whl).split('-', 1)[0]
+                    extra_dirs = PACKAGE_LIB_DIRS.get(whl_name, [])
+                    for extra_dir in extra_dirs:
+                        search_path.append(os.path.join(whl, extra_dir.replace('/', os.path.sep)))
+
+            target_path = os.path.join(builddir, basename)
+            self.copy_with_dependencies(source_path, target_path, search_path)
+
+        # Copy over the tcl directory.
+        #TODO: get this to work on non-Windows platforms.
+        if sys.platform == "win32" and platform.startswith('win'):
+            tcl_dir = os.path.join(sys.prefix, 'tcl')
+            tkinter_name = 'tkinter' if sys.version_info >= (3, 0) else 'Tkinter'
+
+            if os.path.isdir(tcl_dir) and tkinter_name in freezer_modules:
+                self.announce('Copying Tcl files', distutils.log.INFO)
+                os.makedirs(os.path.join(builddir, 'tcl'))
+
+                for dir in os.listdir(tcl_dir):
+                    sub_dir = os.path.join(tcl_dir, dir)
+                    if os.path.isdir(sub_dir):
+                        target_dir = os.path.join(builddir, 'tcl', dir)
+                        self.announce('copying {0} -> {1}'.format(sub_dir, target_dir))
+                        shutil.copytree(sub_dir, target_dir)
+
+        # Extract any other data files from dependency packages.
+        for module, datadesc in self.package_data_dirs.items():
+            if module not in freezer_modules:
+                continue
+
+            self.announce('Copying data files for module: {}'.format(module), distutils.log.INFO)
+
+            # OK, find out in which .whl this occurs.
+            for whl in wheelpaths:
+                whlfile = self._get_zip_file(whl)
+                filenames = whlfile.namelist()
+                for source_pattern, target_dir, flags in datadesc:
+                    srcglob = p3d.GlobPattern(source_pattern)
+                    source_dir = os.path.dirname(source_pattern)
+                    # Relocate the target dir to the build directory.
+                    target_dir = target_dir.replace('/', os.sep)
+                    target_dir = os.path.join(builddir, target_dir)
+
+                    for wf in filenames:
+                        if wf.lower().startswith(source_dir.lower() + '/'):
+                            if not srcglob.matches(wf.lower()):
+                                continue
+                            wf = wf.replace('/', os.sep)
+                            relpath = wf[len(source_dir) + 1:]
+                            source_path = os.path.join(whl, wf)
+                            target_path = os.path.join(target_dir, relpath)
+                            self.copy(source_path, target_path)
+
+                            if 'PKG_DATA_MAKE_EXECUTABLE' in flags:
+                                mode = os.stat(target_path).st_mode
+                                mode |= stat.S_IXUSR | stat.S_IXGRP | stat.S_IXOTH
+                                os.chmod(target_path, mode)
+
+        # Copy Game Files
+        self.announce('Copying game files for platform: {}'.format(platform), distutils.log.INFO)
+        ignore_copy_list = [
+            '**/__pycache__/**',
+            '**/*.pyc',
+            '{}/**'.format(self.build_base),
+        ]
+        ignore_copy_list += self.exclude_patterns
+        ignore_copy_list += freezer_modpaths
+        ignore_copy_list += self.extra_prc_files
+        ignore_copy_list = [p3d.GlobPattern(p3d.Filename.from_os_specific(i).get_fullpath()) for i in ignore_copy_list]
+
+        include_copy_list = [p3d.GlobPattern(i) for i in self.include_patterns]
+
+        def check_pattern(src, pattern_list):
+            # Normalize file paths across platforms
+            fn = p3d.Filename.from_os_specific(os.path.normpath(src))
+            path = fn.get_fullpath()
+            fn.make_absolute()
+            abspath = fn.get_fullpath()
+
+            for pattern in pattern_list:
+                # If the pattern is absolute, match against the absolute filename.
+                if pattern.pattern[0] == '/':
+                    #print('check ignore: {} {} {}'.format(pattern, src, pattern.matches_file(abspath)))
+                    if pattern.matches_file(abspath):
+                        return True
+                else:
+                    #print('check ignore: {} {} {}'.format(pattern, src, pattern.matches_file(path)))
+                    if pattern.matches_file(path):
+                        return True
+            return False
+
+        def check_file(fname):
+            return check_pattern(fname, include_copy_list) and \
+                not check_pattern(fname, ignore_copy_list)
+
+        def copy_file(src, dst):
+            src = os.path.normpath(src)
+            dst = os.path.normpath(dst)
+
+            if not check_file(src):
+                self.announce('skipping file {}'.format(src))
+                return
+
+            dst_dir = os.path.dirname(dst)
+            if not os.path.exists(dst_dir):
+                os.makedirs(dst_dir)
+
+            ext = os.path.splitext(src)[1]
+            if not ext:
+                ext = os.path.basename(src)
+
+            if ext in self.file_handlers:
+                buildscript = self.file_handlers[ext]
+                self.announce('running {} on src ({})'.format(buildscript.__name__, src))
+                try:
+                    dst = self.file_handlers[ext](self, src, dst)
+                except Exception as err:
+                    self.announce('{}'.format(err), distutils.log.ERROR)
+            else:
+                self.announce('copying {0} -> {1}'.format(src, dst))
+                shutil.copyfile(src, dst)
+
+        def update_path(path):
+            normpath = p3d.Filename.from_os_specific(os.path.normpath(src)).c_str()
+            for inputpath, outputpath in self.rename_paths.items():
+                if normpath.startswith(inputpath):
+                    normpath = normpath.replace(inputpath, outputpath, 1)
+            return p3d.Filename(normpath).to_os_specific()
+
+        rootdir = os.getcwd()
+        for dirname, subdirlist, filelist in os.walk(rootdir):
+            dirpath = os.path.relpath(dirname, rootdir)
+            for fname in filelist:
+                src = os.path.join(dirpath, fname)
+                dst = os.path.join(builddir, update_path(src))
+
+                copy_file(src, dst)
+
+        # Bundle into an .app on macOS
+        if self.macos_main_app and 'macosx' in platform:
+            self.bundle_macos_app(builddir)
+
+    def add_dependency(self, name, target_dir, search_path, referenced_by):
+        """ Searches for the given DLL on the search path.  If it exists,
+        copies it to the target_dir. """
+
+        if os.path.exists(os.path.join(target_dir, name)):
+            # We've already added it earlier.
+            return
+
+        for dep in self.exclude_dependencies:
+            if dep.matches_file(name):
+                return
+
+        for dir in search_path:
+            source_path = os.path.join(dir, name)
+
+            if os.path.isfile(source_path):
+                target_path = os.path.join(target_dir, name)
+                self.copy_with_dependencies(source_path, target_path, search_path)
+                return
+
+            elif '.whl' in source_path:
+                # Check whether the file exists inside the wheel.
+                whl, wf = source_path.split('.whl' + os.path.sep)
+                whl += '.whl'
+                whlfile = self._get_zip_file(whl)
+
+                # Normalize the path separator
+                wf = os.path.normpath(wf).replace(os.path.sep, '/')
+
+                # Look case-insensitively.
+                namelist = whlfile.namelist()
+                namelist_lower = [file.lower() for file in namelist]
+
+                if wf.lower() in namelist_lower:
+                    # We have a match.  Change it to the correct case.
+                    wf = namelist[namelist_lower.index(wf.lower())]
+                    source_path = os.path.join(whl, wf)
+                    target_path = os.path.join(target_dir, os.path.basename(wf))
+                    self.copy_with_dependencies(source_path, target_path, search_path)
+                    return
+
+        # If we didn't find it, look again, but case-insensitively.
+        name_lower = name.lower()
+
+        for dir in search_path:
+            if os.path.isdir(dir):
+                files = os.listdir(dir)
+                files_lower = [file.lower() for file in files]
+
+                if name_lower in files_lower:
+                    name = files[files_lower.index(name_lower)]
+                    source_path = os.path.join(dir, name)
+                    target_path = os.path.join(target_dir, name)
+                    self.copy_with_dependencies(source_path, target_path, search_path)
+
+        # Warn if we can't find it, but only once.
+        self.warn("could not find dependency {0} (referenced by {1})".format(name, referenced_by))
+        self.exclude_dependencies.append(p3d.GlobPattern(name.lower()))
+
+    def copy(self, source_path, target_path):
+        """ Copies source_path to target_path.
+
+        source_path may be located inside a .whl file. """
+
+        try:
+            self.announce('copying {0} -> {1}'.format(os.path.relpath(source_path, self.build_base), os.path.relpath(target_path, self.build_base)))
+        except ValueError:
+            # No relative path (e.g., files on different drives in Windows), just print absolute paths instead
+            self.announce('copying {0} -> {1}'.format(source_path, target_path))
+
+        # Make the directory if it does not yet exist.
+        target_dir = os.path.dirname(target_path)
+        if not os.path.isdir(target_dir):
+            os.makedirs(target_dir)
+
+        # Copy the file, and open it for analysis.
+        if '.whl' in source_path:
+            # This was found in a wheel, extract it
+            whl, wf = source_path.split('.whl' + os.path.sep)
+            whl += '.whl'
+            whlfile = self._get_zip_file(whl)
+            data = whlfile.read(wf.replace(os.path.sep, '/'))
+            with open(target_path, 'wb') as f:
+                f.write(data)
+        else:
+            # Regular file, copy it
+            shutil.copyfile(source_path, target_path)
+
+    def copy_with_dependencies(self, source_path, target_path, search_path):
+        """ Copies source_path to target_path.  It also scans source_path for
+        any dependencies, which are located along the given search_path and
+        copied to the same directory as target_path.
+
+        source_path may be located inside a .whl file. """
+
+        self.copy(source_path, target_path)
+
+        source_dir = os.path.dirname(source_path)
+        target_dir = os.path.dirname(target_path)
+        base = os.path.basename(target_path)
+        self.copy_dependencies(target_path, target_dir, search_path + [source_dir], base)
+
+    def copy_dependencies(self, target_path, target_dir, search_path, referenced_by):
+        """ Copies the dependencies of target_path into target_dir. """
+
+        fp = open(target_path, 'rb+')
+
+        # What kind of magic does the file contain?
+        deps = []
+        magic = fp.read(4)
+        if magic.startswith(b'MZ'):
+            # It's a Windows DLL or EXE file.
+            pe = pefile.PEFile()
+            pe.read(fp)
+            for lib in pe.imports:
+                if not lib.lower().startswith('api-ms-win-'):
+                    deps.append(lib)
+
+        elif magic == b'\x7FELF':
+            # Elf magic.  Used on (among others) Linux and FreeBSD.
+            deps = self._read_dependencies_elf(fp, target_dir, search_path)
+
+        elif magic in (b'\xCE\xFA\xED\xFE', b'\xCF\xFA\xED\xFE'):
+            # A Mach-O file, as used on macOS.
+            deps = self._read_dependencies_macho(fp, '<', flatten=True)
+
+        elif magic in (b'\xFE\xED\xFA\xCE', b'\xFE\xED\xFA\xCF'):
+            rel_dir = os.path.relpath(target_dir, os.path.dirname(target_path))
+            deps = self._read_dependencies_macho(fp, '>', flatten=True)
+
+        elif magic in (b'\xCA\xFE\xBA\xBE', b'\xBE\xBA\xFE\xCA'):
+            # A fat file, containing multiple Mach-O binaries.  In the future,
+            # we may want to extract the one containing the architecture we
+            # are building for.
+            deps = self._read_dependencies_fat(fp, False, flatten=True)
+
+        elif magic in (b'\xCA\xFE\xBA\xBF', b'\xBF\xBA\xFE\xCA'):
+            # A 64-bit fat file.
+            deps = self._read_dependencies_fat(fp, True, flatten=True)
+
+        # If we discovered any dependencies, recursively add those.
+        for dep in deps:
+            self.add_dependency(dep, target_dir, search_path, referenced_by)
+
+    def _read_dependencies_elf(self, elf, origin, search_path):
+        """ Having read the first 4 bytes of the ELF file, fetches the
+        dependent libraries and returns those as a list. """
+
+        ident = elf.read(12)
+
+        # Make sure we read in the correct endianness and integer size
+        byte_order = "<>"[ord(ident[1:2]) - 1]
+        elf_class = ord(ident[0:1]) - 1 # 0 = 32-bits, 1 = 64-bits
+        header_struct = byte_order + ("HHIIIIIHHHHHH", "HHIQQQIHHHHHH")[elf_class]
+        section_struct = byte_order + ("4xI8xIII8xI", "4xI16xQQI12xQ")[elf_class]
+        dynamic_struct = byte_order + ("iI", "qQ")[elf_class]
+
+        type, machine, version, entry, phoff, shoff, flags, ehsize, phentsize, phnum, shentsize, shnum, shstrndx \
+          = struct.unpack(header_struct, elf.read(struct.calcsize(header_struct)))
+        dynamic_sections = []
+        string_tables = {}
+
+        # Seek to the section header table and find the .dynamic section.
+        elf.seek(shoff)
+        for i in range(shnum):
+            type, offset, size, link, entsize = struct.unpack_from(section_struct, elf.read(shentsize))
+            if type == 6 and link != 0: # DYNAMIC type, links to string table
+                dynamic_sections.append((offset, size, link, entsize))
+                string_tables[link] = None
+
+        # Read the relevant string tables.
+        for idx in string_tables.keys():
+            elf.seek(shoff + idx * shentsize)
+            type, offset, size, link, entsize = struct.unpack_from(section_struct, elf.read(shentsize))
+            if type != 3: continue
+            elf.seek(offset)
+            string_tables[idx] = elf.read(size)
+
+        # Loop through the dynamic sections and rewrite it if it has an rpath/runpath.
+        needed = []
+        rpath = []
+        for offset, size, link, entsize in dynamic_sections:
+            elf.seek(offset)
+            data = elf.read(entsize)
+            tag, val = struct.unpack_from(dynamic_struct, data)
+
+            # Read tags until we find a NULL tag.
+            while tag != 0:
+                if tag == 1: # A NEEDED entry.  Read it from the string table.
+                    string = string_tables[link][val : string_tables[link].find(b'\0', val)]
+                    needed.append(string.decode('utf-8'))
+
+                elif tag == 15 or tag == 29:
+                    # An RPATH or RUNPATH entry.
+                    string = string_tables[link][val : string_tables[link].find(b'\0', val)]
+                    rpath += [
+                        os.path.normpath(i.decode('utf-8').replace('$ORIGIN', origin))
+                        for i in string.split(b':')
+                    ]
+
+                data = elf.read(entsize)
+                tag, val = struct.unpack_from(dynamic_struct, data)
+        elf.close()
+
+        search_path += rpath
+        return needed
+
+    def _read_dependencies_macho(self, fp, endian, flatten=False):
+        """ Having read the first 4 bytes of the Mach-O file, fetches the
+        dependent libraries and returns those as a list.
+
+        If flatten is True, if the dependencies contain paths like
+        @loader_path/../.dylibs/libsomething.dylib, it will rewrite them to
+        instead contain @loader_path/libsomething.dylib if possible.
+        This requires the file pointer to be opened in rb+ mode. """
+
+        cputype, cpusubtype, filetype, ncmds, sizeofcmds, flags = \
+            struct.unpack(endian + 'IIIIII', fp.read(24))
+
+        is_64bit = (cputype & 0x1000000) != 0
+        if is_64bit:
+            fp.read(4)
+
+        # After the header, we get a series of linker commands.  We just
+        # iterate through them and gather up the LC_LOAD_DYLIB commands.
+        load_dylibs = []
+        for i in range(ncmds):
+            cmd, cmd_size = struct.unpack(endian + 'II', fp.read(8))
+            cmd_data = fp.read(cmd_size - 8)
+            cmd &= ~0x80000000
+
+            if cmd == 0x0c: # LC_LOAD_DYLIB
+                dylib = cmd_data[16:].decode('ascii').split('\x00', 1)[0]
+                orig = dylib
+
+                if dylib.startswith('@loader_path/../Frameworks/'):
+                    dylib = dylib.replace('@loader_path/../Frameworks/', '')
+                elif dylib.startswith('@executable_path/../Frameworks/'):
+                    dylib = dylib.replace('@executable_path/../Frameworks/', '')
+                elif dylib.startswith('@loader_path/'):
+                    dylib = dylib.replace('@loader_path/', '')
+
+                    # Do we need to flatten the relative reference?
+                    if '/' in dylib and flatten:
+                        new_dylib = '@loader_path/' + os.path.basename(dylib)
+                        str_size = len(cmd_data) - 16
+                        if len(new_dylib) < str_size:
+                            fp.seek(-str_size, os.SEEK_CUR)
+                            fp.write(new_dylib.encode('ascii').ljust(str_size, b'\0'))
+                        else:
+                            self.warn('Unable to rewrite dependency {}'.format(orig))
+
+                load_dylibs.append(dylib)
+
+        return load_dylibs
+
+    def _read_dependencies_fat(self, fp, is_64bit, flatten=False):
+        num_fat, = struct.unpack('>I', fp.read(4))
+
+        # After the header we get a table of executables in this fat file,
+        # each one with a corresponding offset into the file.
+        offsets = []
+        for i in range(num_fat):
+            if is_64bit:
+                cputype, cpusubtype, offset, size, align = \
+                    struct.unpack('>QQQQQ', fp.read(40))
+            else:
+                cputype, cpusubtype, offset, size, align = \
+                    struct.unpack('>IIIII', fp.read(20))
+            offsets.append(offset)
+
+        # Go through each of the binaries in the fat file.
+        deps = []
+        for offset in offsets:
+            # Add 4, since it expects we've already read the magic.
+            fp.seek(offset)
+            magic = fp.read(4)
+
+            if magic in (b'\xCE\xFA\xED\xFE', b'\xCF\xFA\xED\xFE'):
+                endian = '<'
+            elif magic in (b'\xFE\xED\xFA\xCE', b'\xFE\xED\xFA\xCF'):
+                endian = '>'
+            else:
+                # Not a Mach-O file we can read.
+                continue
+
+            for dep in self._read_dependencies_macho(fp, endian, flatten=flatten):
+                if dep not in deps:
+                    deps.append(dep)
+
+        return deps
+
+    def expand_path(self, path, platform):
+        "Substitutes variables in the given path string."
+
+        if path is None:
+            return None
+
+        t = string.Template(path)
+        if platform.startswith('win'):
+            return t.substitute(HOME='~', USER_APPDATA='~/AppData/Local')
+        elif platform.startswith('macosx'):
+            return t.substitute(HOME='~', USER_APPDATA='~/Documents')
+        else:
+            return t.substitute(HOME='~', USER_APPDATA='~/.local/share')
+
+
+class bdist_apps(setuptools.Command):
+    DEFAULT_INSTALLERS = {
+        'manylinux1_x86_64': ['gztar'],
+        'manylinux1_i686': ['gztar'],
+        # Everything else defaults to ['zip']
+    }
+
+    description = 'bundle built Panda3D applications into distributable forms'
+    user_options = build_apps.user_options + [
+        ('dist-dir=', 'd', 'directory to put final built distributions in'),
+        ('skip-build', None, 'skip rebuilding everything (for testing/debugging)'),
+    ]
+
+    def _build_apps_options(self):
+        return [opt[0].replace('-', '_').replace('=', '') for opt in build_apps.user_options]
+
+    def initialize_options(self):
+        self.installers = {}
+        self.dist_dir = os.path.join(os.getcwd(), 'dist')
+        self.skip_build = False
+        for opt in self._build_apps_options():
+            setattr(self, opt, None)
+
+    def finalize_options(self):
+        # We need to massage the inputs a bit in case they came from a
+        # setup.cfg file.
+        self.installers = {
+            key: _parse_list(value)
+            for key, value in _parse_dict(self.installers).items()
+        }
+
+    def _get_archive_basedir(self):
+        return self.distribution.get_name()
+
+    def create_zip(self, basename, build_dir):
+        import zipfile
+
+        base_dir = self._get_archive_basedir()
+
+        with zipfile.ZipFile(basename+'.zip', 'w', compression=zipfile.ZIP_DEFLATED) as zf:
+            zf.write(build_dir, base_dir)
+
+            for dirpath, dirnames, filenames in os.walk(build_dir):
+                for name in sorted(dirnames):
+                    path = os.path.normpath(os.path.join(dirpath, name))
+                    zf.write(path, path.replace(build_dir, base_dir, 1))
+                for name in filenames:
+                    path = os.path.normpath(os.path.join(dirpath, name))
+                    if os.path.isfile(path):
+                        zf.write(path, path.replace(build_dir, base_dir, 1))
+
+    def create_tarball(self, basename, build_dir, tar_compression):
+        import tarfile
+
+        base_dir = self._get_archive_basedir()
+        build_cmd = self.get_finalized_command('build_apps')
+        binary_names = list(build_cmd.console_apps.keys()) + list(build_cmd.gui_apps.keys())
+
+        def tarfilter(tarinfo):
+            if tarinfo.isdir() or os.path.basename(tarinfo.name) in binary_names:
+                tarinfo.mode = 0o755
+            else:
+                tarinfo.mode = 0o644
+            return tarinfo
+
+        with tarfile.open('{}.tar.{}'.format(basename, tar_compression), 'w|{}'.format(tar_compression)) as tf:
+            tf.add(build_dir, base_dir, filter=tarfilter)
+
+    def create_nsis(self, basename, build_dir, is_64bit):
+        # Get a list of build applications
+        build_cmd = self.get_finalized_command('build_apps')
+        apps = build_cmd.gui_apps.copy()
+        apps.update(build_cmd.console_apps)
+        apps = [
+            '{}.exe'.format(i)
+            for i in apps
+        ]
+
+        fullname = self.distribution.get_fullname()
+        shortname = self.distribution.get_name()
+
+        # Create the .nsi installer script
+        nsifile = p3d.Filename(build_cmd.build_base, shortname + ".nsi")
+        nsifile.unlink()
+        nsi = open(nsifile.to_os_specific(), "w")
+
+        # Some global info
+        nsi.write('Name "%s"\n' % shortname)
+        nsi.write('OutFile "%s"\n' % (fullname+'.exe'))
+        if is_64bit:
+            nsi.write('InstallDir "$PROGRAMFILES64\\%s"\n' % shortname)
+        else:
+            nsi.write('InstallDir "$PROGRAMFILES\\%s"\n' % shortname)
+        nsi.write('SetCompress auto\n')
+        nsi.write('SetCompressor lzma\n')
+        nsi.write('ShowInstDetails nevershow\n')
+        nsi.write('ShowUninstDetails nevershow\n')
+        nsi.write('InstType "Typical"\n')
+
+        # Tell Vista that we require admin rights
+        nsi.write('RequestExecutionLevel admin\n')
+        nsi.write('\n')
+
+        # TODO offer run and desktop shortcut after we figure out how to deal
+        # with multiple apps
+
+        nsi.write('!include "MUI2.nsh"\n')
+        nsi.write('!define MUI_ABORTWARNING\n')
+        nsi.write('\n')
+        nsi.write('Var StartMenuFolder\n')
+        nsi.write('!insertmacro MUI_PAGE_WELCOME\n')
+        # TODO license file
+        nsi.write('!insertmacro MUI_PAGE_DIRECTORY\n')
+        nsi.write('!insertmacro MUI_PAGE_STARTMENU Application $StartMenuFolder\n')
+        nsi.write('!insertmacro MUI_PAGE_INSTFILES\n')
+        nsi.write('!insertmacro MUI_PAGE_FINISH\n')
+        nsi.write('!insertmacro MUI_UNPAGE_WELCOME\n')
+        nsi.write('!insertmacro MUI_UNPAGE_CONFIRM\n')
+        nsi.write('!insertmacro MUI_UNPAGE_INSTFILES\n')
+        nsi.write('!insertmacro MUI_UNPAGE_FINISH\n')
+        nsi.write('!insertmacro MUI_LANGUAGE "English"\n')
+
+        # This section defines the installer.
+        nsi.write('Section "" SecCore\n')
+        nsi.write('  SetOutPath "$INSTDIR"\n')
+        curdir = ""
+        for root, dirs, files in os.walk(build_dir):
+            for name in files:
+                basefile = p3d.Filename.fromOsSpecific(os.path.join(root, name))
+                file = p3d.Filename(basefile)
+                file.makeAbsolute()
+                file.makeRelativeTo(build_dir)
+                outdir = file.getDirname().replace('/', '\\')
+                if curdir != outdir:
+                    nsi.write('  SetOutPath "$INSTDIR\\%s"\n' % outdir)
+                    curdir = outdir
+                nsi.write('  File "%s"\n' % (basefile.toOsSpecific()))
+        nsi.write('  SetOutPath "$INSTDIR"\n')
+        nsi.write('  WriteUninstaller "$INSTDIR\\Uninstall.exe"\n')
+        nsi.write('  ; Start menu items\n')
+        nsi.write('  !insertmacro MUI_STARTMENU_WRITE_BEGIN Application\n')
+        nsi.write('    CreateDirectory "$SMPROGRAMS\\$StartMenuFolder"\n')
+        for app in apps:
+            nsi.write('    CreateShortCut "$SMPROGRAMS\\$StartMenuFolder\\%s.lnk" "$INSTDIR\\%s"\n' % (shortname, app))
+        nsi.write('    CreateShortCut "$SMPROGRAMS\\$StartMenuFolder\\Uninstall.lnk" "$INSTDIR\\Uninstall.exe"\n')
+        nsi.write('  !insertmacro MUI_STARTMENU_WRITE_END\n')
+        nsi.write('SectionEnd\n')
+
+        # This section defines the uninstaller.
+        nsi.write('Section Uninstall\n')
+        nsi.write('  RMDir /r "$INSTDIR"\n')
+        nsi.write('  ; Desktop icon\n')
+        nsi.write('  Delete "$DESKTOP\\%s.lnk"\n' % shortname)
+        nsi.write('  ; Start menu items\n')
+        nsi.write('  !insertmacro MUI_STARTMENU_GETFOLDER Application $StartMenuFolder\n')
+        nsi.write('  RMDir /r "$SMPROGRAMS\\$StartMenuFolder"\n')
+        nsi.write('SectionEnd\n')
+        nsi.close()
+
+        cmd = ['makensis']
+        for flag in ["V2"]:
+            cmd.append(
+                '{}{}'.format('/' if sys.platform.startswith('win') else '-', flag)
+            )
+        cmd.append(nsifile.to_os_specific())
+        subprocess.check_call(cmd)
+
+    def run(self):
+        build_cmd = self.distribution.get_command_obj('build_apps')
+        for opt in self._build_apps_options():
+            optval = getattr(self, opt)
+            if optval is not None:
+                setattr(build_cmd, opt, optval)
+        build_cmd.finalize_options()
+        if not self.skip_build:
+            self.run_command('build_apps')
+
+        platforms = build_cmd.platforms
+        build_base = build_cmd.build_base
+        os.makedirs(self.dist_dir, exist_ok=True)
+        os.chdir(self.dist_dir)
+
+        for platform in platforms:
+            build_dir = os.path.join(build_base, platform)
+            basename = '{}_{}'.format(self.distribution.get_fullname(), platform)
+            installers = self.installers.get(platform, self.DEFAULT_INSTALLERS.get(platform, ['zip']))
+
+            for installer in installers:
+                self.announce('\nBuilding {} for platform: {}'.format(installer, platform), distutils.log.INFO)
+
+                if installer == 'zip':
+                    self.create_zip(basename, build_dir)
+                elif installer in ('gztar', 'bztar', 'xztar'):
+                    compress = installer.replace('tar', '')
+                    if compress == 'bz':
+                        compress = 'bz2'
+
+                    self.create_tarball(basename, build_dir, compress)
+                elif installer == 'nsis':
+                    if not platform.startswith('win'):
+                        self.announce(
+                            '\tNSIS installer not supported for platform: {}'.format(platform),
+                            distutils.log.ERROR
+                        )
+                        continue
+                    try:
+                        subprocess.call(['makensis', '--version'])
+                    except OSError:
+                        self.announce(
+                            '\tCould not find makensis tool that is required to build NSIS installers',
+                            distutils.log.ERROR
+                        )
+                        # continue
+                    is_64bit = platform == 'win_amd64'
+                    self.create_nsis(basename, build_dir, is_64bit)
+
+                else:
+                    self.announce('\tUnknown installer: {}'.format(installer), distutils.log.ERROR)

+ 908 - 0
direct/src/dist/pefile.py

@@ -0,0 +1,908 @@
+""" Tools for manipulating Portable Executable files.
+
+This can be used, for example, to extract a list of dependencies from an .exe
+or .dll file, or to add version information and an icon resource to it. """
+
+__all__ = ["PEFile"]
+
+from struct import Struct, unpack, pack, pack_into
+from collections import namedtuple
+from array import array
+import time
+from io import BytesIO
+import sys
+
+if sys.version_info >= (3, 0):
+    unicode = str
+    unichr = chr
+
+# Define some internally used structures.
+RVASize = namedtuple('RVASize', ('addr', 'size'))
+impdirtab = namedtuple('impdirtab', ('lookup', 'timdat', 'forward', 'name', 'impaddr'))
+expdirtab = namedtuple('expdirtab', ('flags', 'timdat', 'majver', 'minver', 'name', 'ordinal_base', 'nentries', 'nnames', 'entries', 'names', 'ordinals'))
+
+
+def _unpack_zstring(mem, offs=0):
+    "Read a zero-terminated string from memory."
+    c = mem[offs]
+    str = ""
+    while c:
+        str += chr(c)
+        offs += 1
+        c = mem[offs]
+    return str
+
+def _unpack_wstring(mem, offs=0):
+    "Read a UCS-2 string from memory."
+    name_len, = unpack('<H', mem[offs:offs+2])
+    name = ""
+    for i in range(name_len):
+        offs += 2
+        name += unichr(*unpack('<H', mem[offs:offs+2]))
+    return name
+
+def _padded(n, boundary):
+    align = n % boundary
+    if align:
+        n += boundary - align
+    return n
+
+
+class Section(object):
+    _header = Struct('<8sIIIIIIHHI')
+
+    modified = True
+
+    def read_header(self, fp):
+        name, vsize, vaddr, size, scnptr, relptr, lnnoptr, nreloc, nlnno, flags = \
+            self._header.unpack(fp.read(40))
+
+        self.name = name.rstrip(b'\x00')
+        self.vaddr = vaddr # Base virtual address to map to.
+        self.vsize = vsize
+        self.offset = scnptr # Offset of the section in the file.
+        self.size = size
+        self.flags = flags
+
+        self.modified = False
+
+    def write_header(self, fp):
+        fp.write(self._header.pack(self.name, self.vsize, self.vaddr,
+                                   self.size, self.offset, 0, 0, 0, 0,
+                                   self.flags))
+
+    def __repr__(self):
+        return "<section '%s' memory %x-%x>" % (self.name, self.vaddr, self.vaddr + self.vsize)
+
+    def __gt__(self, other):
+        return self.vaddr > other.vaddr
+
+    def __lt__(self, other):
+        return self.vaddr < other.vaddr
+
+
+class DataResource(object):
+    """ A resource entry in the resource table. """
+
+    # Resource types.
+    cursor = 1
+    bitmap = 2
+    icon = 3
+    menu = 4
+    dialog = 5
+    string = 6
+    font_directory = 7
+    font = 8
+    accelerator = 9
+    rcdata = 10
+    message_table = 11
+    cursor_group = 12
+    icon_group = 14
+    version = 16
+    dlg_include = 17
+    plug_play = 19
+    vxd = 20
+    animated_cursor = 21
+    animated_icon = 22
+    html = 23
+    manifest = 24
+
+    def __init__(self):
+        self._ident = ()
+        self.data = None
+        self.code_page = 0
+
+    @property
+    def encoding(self):
+        if self.code_page == 0:
+            return 'ascii'
+        else:
+            return 'cp%d' % (self.code_page)
+
+    def get_data(self):
+        return self.data
+
+    def get_text(self, errors='strict'):
+        return self.data.decode(self.encoding, errors)
+
+
+class IconGroupResource(object):
+    code_page = 0
+    type = 14
+    _entry = Struct('<BBBxHHIH')
+    Icon = namedtuple('Icon', ('width', 'height', 'planes', 'bpp', 'size', 'id'))
+
+    def __init__(self):
+        self.icons = []
+
+    def add_icon(self, *args, **kwargs):
+        self.icons.append(self.Icon(*args, **kwargs))
+
+    def get_data(self):
+        data = bytearray(pack('<HHH', 0, 1, len(self.icons)))
+
+        for width, height, planes, bpp, size, id in self.icons:
+            colors = 1 << (planes * bpp)
+            if colors >= 256:
+                colors = 0
+            if width >= 256:
+                width = 0
+            if height >= 256:
+                height = 0
+            data += self._entry.pack(width, height, colors, planes, bpp, size, id)
+        return data
+
+    def unpack_from(self, data, offs=0):
+        type, count = unpack('<HH', data[offs+2:offs+6])
+        offs += 6
+        for i in range(count):
+            width, height, colors, planes, bpp, size, id = \
+                self._entry.unpack(data[offs:offs+14])
+            if width == 0:
+                width = 256
+            if height == 0:
+                height = 256
+            self.icons.append(self.Icon(width, height, planes, bpp, size, id))
+            offs += 14
+
+
+class VersionInfoResource(object):
+    code_page = 0
+    type = 16
+
+    def __init__(self):
+        self.string_info = {}
+        self.var_info = {}
+        self.signature = 0xFEEF04BD
+        self.struct_version = 0x10000
+        self.file_version = (0, 0, 0, 0)
+        self.product_version = (0, 0, 0, 0)
+        self.file_flags_mask = 0x3f
+        self.file_flags = 0
+        self.file_os = 0x40004 # Windows NT
+        self.file_type = 1 # Application
+        self.file_subtype = 0
+        self.file_date = (0, 0)
+
+    def get_data(self):
+        # The first part of the header is pretty much fixed - we'll go
+        # back later to write the struct size.
+        data = bytearray(b'\x00\x004\x00\x00\x00V\x00S\x00_\x00V\x00E\x00R\x00S\x00I\x00O\x00N\x00_\x00I\x00N\x00F\x00O\x00\x00\x00\x00\x00')
+        data += pack('<13I', self.signature, self.struct_version,
+                             self.file_version[1] | (self.file_version[0] << 16),
+                             self.file_version[3] | (self.file_version[2] << 16),
+                             self.product_version[1] | (self.product_version[0] << 16),
+                             self.product_version[3] | (self.product_version[2] << 16),
+                             self.file_flags_mask, self.file_flags,
+                             self.file_os, self.file_type, self.file_subtype,
+                             self.file_date[0], self.file_date[1])
+
+        self._pack_info(data, 'StringFileInfo', self.string_info)
+        self._pack_info(data, 'VarFileInfo', self.var_info)
+        data[0:2] = pack('<H', len(data))
+        return data
+
+    def _pack_info(self, data, key, value):
+        offset = len(data)
+
+        if isinstance(value, dict):
+            type = 1
+            value_length = 0
+        elif isinstance(value, bytes) or isinstance(value, unicode):
+            type = 1
+            value_length = len(value) * 2 + 2
+        else:
+            type = 0
+            value_length = len(value)
+
+        data += pack('<HHH', 0, value_length, type)
+
+        for c in key:
+            data += pack('<H', ord(c))
+        data += b'\x00\x00'
+        if len(data) & 2:
+            data += b'\x00\x00'
+        assert len(data) & 3 == 0
+
+        if isinstance(value, dict):
+            for key2, value2 in sorted(value.items(), key=lambda x:x[0]):
+                self._pack_info(data, key2, value2)
+        elif isinstance(value, bytes) or isinstance(value, unicode):
+            for c in value:
+                data += pack('<H', ord(c))
+            data += b'\x00\x00'
+        else:
+            data += value
+            if len(data) & 1:
+                data += b'\x00'
+
+        if len(data) & 2:
+            data += b'\x00\x00'
+        assert len(data) & 3 == 0
+
+        data[offset:offset+2] = pack('<H', len(data) - offset)
+
+    def unpack_from(self, data):
+        length, value_length = unpack('<HH', data[0:4])
+        offset = 40 + value_length + (value_length & 1)
+        dwords = array('I')
+        dwords.fromstring(bytes(data[40:offset]))
+        if len(dwords) > 0:
+            self.signature = dwords[0]
+        if len(dwords) > 1:
+            self.struct_version = dwords[1]
+        if len(dwords) > 3:
+            self.file_version = \
+               (int(dwords[2] >> 16), int(dwords[2] & 0xffff),
+                int(dwords[3] >> 16), int(dwords[3] & 0xffff))
+        if len(dwords) > 5:
+            self.product_version = \
+               (int(dwords[4] >> 16), int(dwords[4] & 0xffff),
+                int(dwords[5] >> 16), int(dwords[5] & 0xffff))
+        if len(dwords) > 7:
+            self.file_flags_mask = dwords[6]
+            self.file_flags = dwords[7]
+        if len(dwords) > 8:
+            self.file_os = dwords[8]
+        if len(dwords) > 9:
+            self.file_type = dwords[9]
+        if len(dwords) > 10:
+            self.file_subtype = dwords[10]
+        if len(dwords) > 12:
+            self.file_date = (dwords[11], dwords[12])
+
+        while offset < length:
+            offset += self._unpack_info(self, data, offset)
+
+    def __getitem__(self, key):
+        if key == 'StringFileInfo':
+            return self.string_info
+        elif key == 'VarFileInfo':
+            return self.var_info
+        else:
+            raise KeyError("%s does not exist" % (key))
+
+    def __contains__(self, key):
+        return key in ('StringFileInfo', 'VarFileInfo')
+
+    def _unpack_info(self, dict, data, offset):
+        length, value_length, type = unpack('<HHH', data[offset:offset+6])
+        assert length > 0
+        end = offset + length
+        offset += 6
+        key = ""
+        c, = unpack('<H', data[offset:offset+2])
+        offset += 2
+        while c:
+            key += unichr(c)
+            c, = unpack('<H', data[offset:offset+2])
+            offset += 2
+
+        # Padding bytes to align value to 32-bit boundary.
+        offset = _padded(offset, 4)
+
+        if value_length > 0:
+            # It contains a value.
+            if type:
+                # It's a wchar array value.
+                value = u""
+                c, = unpack('<H', data[offset:offset+2])
+                offset += 2
+                while c:
+                    value += unichr(c)
+                    c, = unpack('<H', data[offset:offset+2])
+                    offset += 2
+            else:
+                # A binary value.
+                value = bytes(data[offset:offset+value_length])
+                offset += value_length
+            dict[key] = value
+        else:
+            # It contains sub-entries.
+            if key not in dict:
+                dict[key] = {}
+            subdict = dict[key]
+            while offset < end:
+                offset += self._unpack_info(subdict, data, offset)
+
+        # Padding bytes to pad value to 32-bit boundary.
+        return _padded(length, 4)
+
+
+class ResourceTable(object):
+    """ A table in the resource directory. """
+
+    _header = Struct('<IIHHHH')
+
+    def __init__(self, ident=()):
+        self.flags = 0
+        self.timdat = 0
+        self.version = (0, 0)
+        self._name_leaves = []
+        self._id_leaves = []
+        self._ident = ident
+        self._strings_size = 0 # Amount of space occupied by table keys.
+        self._descs_size = 0
+
+    def __getitem__(self, key):
+        if isinstance(key, int):
+            leaves = self._id_leaves
+        else:
+            leaves = self._name_leaves
+
+        i = 0
+        while i < len(leaves):
+            idname, leaf = leaves[i]
+            if idname >= key:
+                if key == idname:
+                    return leaf
+                break
+            i += 1
+        if not isinstance(key, int):
+            self._strings_size += _padded(len(key) * 2 + 2, 4)
+        leaf = ResourceTable(ident=self._ident + (key,))
+        leaves.insert(i, (key, leaf))
+        return leaf
+
+    def __setitem__(self, key, value):
+        """ Adds the given item to the table.  Maintains sort order. """
+        if isinstance(key, int):
+            leaves = self._id_leaves
+        else:
+            leaves = self._name_leaves
+
+        if not isinstance(value, ResourceTable):
+            self._descs_size += 16
+
+        value._ident = self._ident + (key,)
+        i = 0
+        while i < len(leaves):
+            idname, leaf = leaves[i]
+            if idname >= key:
+                if key == idname:
+                    if not isinstance(leaves[i][1], ResourceTable):
+                        self._descs_size -= 16
+                    leaves[i] = (key, value)
+                    return
+                break
+            i += 1
+        if not isinstance(key, int):
+            self._strings_size += _padded(len(key) * 2 + 2, 4)
+        leaves.insert(i, (key, value))
+
+    def __len__(self):
+        return len(self._name_leaves) + len(self._id_leaves)
+
+    def __iter__(self):
+        keys = []
+        for name, leaf in self._name_leaves:
+            keys.append(name)
+        for id, leaf in self._id_leaves:
+            keys.append(id)
+        return iter(keys)
+
+    def items(self):
+        return self._name_leaves + self._id_leaves
+
+    def count_resources(self):
+        """Counts all of the resources."""
+        count = 0
+        for key, leaf in self._name_leaves + self._id_leaves:
+            if isinstance(leaf, ResourceTable):
+                count += leaf.count_resources()
+            else:
+                count += 1
+        return count
+
+    def get_nested_tables(self):
+        """Returns all tables in this table and subtables."""
+        # First we yield child tables, then nested tables.  This is the
+        # order in which pack_into assumes the tables will be written.
+        for key, leaf in self._name_leaves + self._id_leaves:
+            if isinstance(leaf, ResourceTable):
+                yield leaf
+
+        for key, leaf in self._name_leaves + self._id_leaves:
+            if isinstance(leaf, ResourceTable):
+                for table in leaf.get_nested_tables():
+                    yield table
+
+    def pack_header(self, data, offs):
+        self._header.pack_into(data, offs, self.flags, self.timdat,
+                               self.version[0], self.version[1],
+                               len(self._name_leaves), len(self._id_leaves))
+
+    def unpack_from(self, mem, addr=0, offs=0):
+        start = addr + offs
+        self.flags, self.timdat, majver, minver, nnames, nids = \
+            self._header.unpack(mem[start:start+16])
+        self.version = (majver, minver)
+        start += 16
+
+        # Subtables/entries specified by string name.
+        self._name_leaves = []
+        for i in range(nnames):
+            name_p, data = unpack('<II', mem[start:start+8])
+            if name_p & 0x80000000:
+                name = _unpack_wstring(mem, addr + (name_p & 0x7fffffff))
+            else:
+                # Not sure what to do with this; I don't have a file with this.
+                name = str(name_p)
+
+            if data & 0x80000000:
+                entry = ResourceTable(self._ident + (name,))
+                entry.unpack_from(mem, addr, data & 0x7fffffff)
+            else:
+                entry = self._unpack_data_entry(mem, addr + data, ident=self._ident+(name,))
+                self._descs_size += 16
+            self._name_leaves.append((name, entry))
+            self._strings_size += _padded(len(name) * 2 + 2, 4)
+            start += 8
+
+        # Subtables/entries specified by integer ID.
+        self._id_leaves = []
+        for i in range(nids):
+            id, data = unpack('<II', mem[start:start+8])
+            if data & 0x80000000:
+                entry = ResourceTable(self._ident + (id,))
+                entry.unpack_from(mem, addr, data & 0x7fffffff)
+            else:
+                entry = self._unpack_data_entry(mem, addr + data, ident=self._ident+(id,))
+                self._descs_size += 16
+            self._id_leaves.append((id, entry))
+            start += 8
+
+    def _unpack_data_entry(self, mem, addr, ident):
+        rva, size, code_page = unpack('<III', mem[addr:addr+12])
+        type, name, lang = ident
+        #print("%s/%s/%s: %s [%s]" % (type, name, lang, size, code_page))
+
+        data = mem[rva:rva+size]
+
+        if type == VersionInfoResource.type:
+            entry = VersionInfoResource()
+            entry.unpack_from(data)
+        elif type == IconGroupResource.type:
+            entry = IconGroupResource()
+            entry.unpack_from(data)
+        else:
+            entry = DataResource()
+            entry.data = data
+            entry.code_page = code_page
+
+
+class PEFile(object):
+
+    imports = ()
+
+    def open(self, fn, mode='r'):
+        if 'b' not in mode:
+            mode += 'b'
+        self.fp = open(fn, mode)
+        self.read(self.fp)
+
+    def close(self):
+        self.fp.close()
+
+    def read(self, fp):
+        """ Reads a PE file from the given file object, which must be opened
+        in binary mode. """
+
+        # Read position of header.
+        fp.seek(0x3c)
+        offset, = unpack('<I', fp.read(4))
+
+        fp.seek(offset)
+        if fp.read(4) != b'PE\0\0':
+            raise ValueError("Invalid PE file.")
+
+        # Read the COFF header.
+        self.machine, nscns, timdat, symptr, nsyms, opthdr, flags = \
+            unpack('<HHIIIHH', fp.read(20))
+
+        if nscns == 0:
+            raise ValueError("No sections found.")
+
+        if not opthdr:
+            raise ValueError("No opthdr found.")
+
+        # Read part of the opthdr.
+        magic, self.code_size, self.initialized_size, self.uninitialized_size = \
+            unpack('<HxxIII', fp.read(16))
+
+        # Read alignments.
+        fp.seek(16, 1)
+        self.section_alignment, self.file_alignment = unpack('<II', fp.read(8))
+
+        # Read header/image sizes.
+        fp.seek(16, 1)
+        self.image_size, self.header_size = unpack('<II', fp.read(8))
+
+        if magic == 0x010b: # 32-bit.
+            fp.seek(28, 1)
+        elif magic == 0x20B: # 64-bit.
+            fp.seek(44, 1)
+        else:
+            raise ValueError("unknown type 0x%x" % (magic))
+
+        self.rva_offset = fp.tell()
+        numrvas, = unpack('<I', fp.read(4))
+
+        self.exp_rva = RVASize(0, 0)
+        self.imp_rva = RVASize(0, 0)
+        self.res_rva = RVASize(0, 0)
+
+        # Locate the relevant tables in memory.
+        if numrvas >= 1:
+            self.exp_rva = RVASize(*unpack('<II', fp.read(8)))
+        if numrvas >= 2:
+            self.imp_rva = RVASize(*unpack('<II', fp.read(8)))
+        if numrvas >= 3:
+            self.res_rva = RVASize(*unpack('<II', fp.read(8)))
+
+        # Skip the rest of the tables.
+        if numrvas >= 4:
+            fp.seek((numrvas - 3) * 8, 1)
+
+        # Loop through the sections to find the ones containing our tables.
+        self.sections = []
+        for i in range(nscns):
+            section = Section()
+            section.read_header(fp)
+            self.sections.append(section)
+
+        self.sections.sort()
+
+        # Read the sections into some kind of virtual memory.
+        self.vmem = bytearray(self.sections[-1].vaddr + self.sections[-1].size)
+        memview = memoryview(self.vmem)
+
+        for section in self.sections:
+            fp.seek(section.offset)
+            fp.readinto(memview[section.vaddr:section.vaddr+section.size])
+
+        # Read the import table.
+        start = self.imp_rva.addr
+        dir = impdirtab(*unpack('<IIIII', self.vmem[start:start+20]))
+
+        imports = []
+        while dir.name and dir.lookup:
+            name = _unpack_zstring(self.vmem, dir.name)
+            imports.append(name)
+
+            start += 20
+            dir = impdirtab(*unpack('<IIIII', self.vmem[start:start+20]))
+
+        # Make it a tuple to indicate we don't support modifying it for now.
+        self.imports = tuple(imports)
+
+        # Read the resource tables from the .rsrc section.
+        self.resources = ResourceTable()
+        if self.res_rva.addr and self.res_rva.size:
+            self.resources.unpack_from(self.vmem, self.res_rva.addr)
+
+    def get_export_address(self, symbol_name):
+        """ Finds the virtual address for a named export symbol. """
+
+        if isinstance(symbol_name, bytes):
+            symbol_name = symbol_name.decode('ascii')
+
+        start = self.exp_rva.addr
+        expdir = expdirtab(*unpack('<IIHHIIIIIII', self.vmem[start:start+40]))
+        if expdir.nnames == 0 or expdir.ordinals == 0 or expdir.names == 0:
+            return None
+
+        nptr = expdir.names
+        optr = expdir.ordinals
+        for i in range(expdir.nnames):
+            name_rva, = unpack('<I', self.vmem[nptr:nptr+4])
+            ordinal, = unpack('<H', self.vmem[optr:optr+2])
+            if name_rva != 0:
+                name = _unpack_zstring(self.vmem, name_rva)
+                if name == symbol_name:
+                    assert ordinal >= 0 and ordinal < expdir.nentries
+                    start = expdir.entries + 4 * ordinal
+                    addr, = unpack('<I', self.vmem[start:start+4])
+                    return addr
+            nptr += 4
+            optr += 2
+
+    def get_address_offset(self, addr):
+        """ Turns an address into a offset relative to the file beginning. """
+
+        section = self.get_address_section(addr)
+        if section is not None:
+            return (addr - section.vaddr) + section.offset
+
+    def get_address_section(self, addr):
+        """ Returns the section that this virtual address belongs to. """
+
+        for section in self.sections:
+            if addr >= section.vaddr and addr < section.vaddr + section.size:
+                return section
+
+    def add_icon(self, icon, ordinal=2):
+        """ Adds an icon resource from the given Icon object.  Requires
+        calling add_resource_section() afterwards. """
+
+        group = IconGroupResource()
+        self.resources[group.type][ordinal][1033] = group
+
+        images = sorted(icon.images.items(), key=lambda x:-x[0])
+        id = 1
+
+        # Write 8-bpp image headers for sizes under 256x256.
+        for size, image in images:
+            if size >= 256:
+                continue
+
+            xorsize = size
+            if xorsize % 4 != 0:
+                xorsize += 4 - (xorsize % 4)
+            andsize = (size + 7) >> 3
+            if andsize % 4 != 0:
+                andsize += 4 - (andsize % 4)
+            datasize = 40 + 256 * 4 + (xorsize + andsize) * size
+            group.add_icon(size, size, 1, 8, datasize, id)
+
+            buf = BytesIO()
+            icon._write_bitmap(buf, image, size, 8)
+
+            res = DataResource()
+            res.data = buf.getvalue()
+            self.resources[3][id][1033] = res
+            id += 1
+
+        # And now the 24/32 bpp versions.
+        for size, image in images:
+            if size > 256:
+                continue
+
+            # Calculate the size so we can write the offset within the file.
+            if image.hasAlpha():
+                bpp = 32
+                xorsize = size * 4
+            else:
+                bpp = 24
+                xorsize = size * 3 + (-(size * 3) & 3)
+            andsize = (size + 7) >> 3
+            if andsize % 4 != 0:
+                andsize += 4 - (andsize % 4)
+            datasize = 40 + (xorsize + andsize) * size
+
+            buf = BytesIO()
+            icon._write_bitmap(buf, image, size, bpp)
+
+            res = DataResource()
+            res.data = buf.getvalue()
+            self.resources[3][id][1033] = res
+            group.add_icon(size, size, 1, bpp, datasize, id)
+            id += 1
+
+    def add_section(self, name, flags, data):
+        """ Adds a new section with the given name, flags and data.  The
+        virtual address space is automatically resized to fit the new data.
+
+        Returns the newly created Section object. """
+
+        if isinstance(name, unicode):
+            name = name.encode('ascii')
+
+        section = Section()
+        section.name = name
+        section.flags = flags
+
+        # Put it at the end of all the other sections.
+        section.offset = 0
+        for s in self.sections:
+            section.offset = max(section.offset, s.offset + s.size)
+
+        # Align the offset.
+        section.offset = _padded(section.offset, self.file_alignment)
+
+        # Find a place to put it in the virtual address space.
+        section.vaddr = len(self.vmem)
+        align = section.vaddr % self.section_alignment
+        if align:
+            pad = self.section_alignment - align
+            self.vmem += bytearray(pad)
+            section.vaddr += pad
+
+        section.vsize = len(data)
+        section.size = _padded(section.vsize, self.file_alignment)
+        self.vmem += data
+        self.sections.append(section)
+
+        # Update the size tallies from the opthdr.
+        self.image_size += _padded(section.vsize, self.section_alignment)
+        if flags & 0x20:
+            self.code_size += section.size
+        if flags & 0x40:
+            self.initialized_size += section.size
+        if flags & 0x80:
+            self.uninitialized_size += section.size
+
+        return section
+
+    def add_version_info(self, file_ver, product_ver, data, lang=1033, codepage=1200):
+        """ Adds a version info resource to the file. """
+
+        if "FileVersion" not in data:
+            data["FileVersion"] = '.'.join(file_ver)
+        if "ProductVersion" not in data:
+            data["ProductVersion"] = '.'.join(product_ver)
+
+        assert len(file_ver) == 4
+        assert len(product_ver) == 4
+
+        res = VersionInfoResource()
+        res.file_version = file_ver
+        res.product_version = product_ver
+        res.string_info = {
+            "%04x%04x" % (lang, codepage): data
+        }
+        res.var_info = {
+            "Translation": bytearray(pack("<HH", lang, codepage))
+        }
+
+        self.resources[16][1][lang] = res
+
+    def add_resource_section(self):
+        """ Adds a resource section to the file containing the resources that
+        were previously added via add_icon et al.  Assumes the file does not
+        contain a resource section yet. """
+
+        # Calculate how much space to reserve.
+        tables = [self.resources] + list(self.resources.get_nested_tables())
+        table_size = 0
+        string_size = 0
+        desc_size = 16 * self.resources.count_resources()
+
+        for table in tables:
+            table._offset = table_size
+            table_size += 16 + 8 * len(table)
+            string_size += table._strings_size
+            desc_size += table._descs_size
+
+        # Now write the actual data.
+        tbl_offs = 0
+        str_offs = table_size
+        desc_offs = str_offs + string_size
+        data_offs = desc_offs + desc_size
+        data = bytearray(data_offs)
+        data_addr = _padded(len(self.vmem), self.section_alignment) + data_offs
+
+        for table in tables:
+            table.pack_header(data, tbl_offs)
+
+            tbl_offs += 16
+
+            for name, leaf in table._name_leaves:
+                if isinstance(leaf, ResourceTable):
+                    pack_into('<II', data, tbl_offs, str_offs | 0x80000000, leaf._offset | 0x80000000)
+                else:
+                    pack_into('<II', data, tbl_offs, str_offs | 0x80000000, desc_offs)
+                    resdata = leaf.get_data()
+                    pack_into('<IIII', data, desc_offs, data_addr, len(resdata), leaf.code_page, 0)
+                    data += resdata
+                    desc_offs += 16
+                    data_addr += len(resdata)
+                    align = len(resdata) & 3
+                    if align:
+                        data += bytearray(4 - align)
+                        data_addr += 4 - align
+                tbl_offs += 8
+
+                # Pack the name into the string table.
+                pack_into('<H', data, str_offs, len(name))
+                str_offs += 2
+                for c in name:
+                    pack_into('<H', data, str_offs, ord(c))
+                    str_offs += 2
+                str_offs = _padded(str_offs, 4)
+
+            for id, leaf in table._id_leaves:
+                if isinstance(leaf, ResourceTable):
+                    pack_into('<II', data, tbl_offs, id, leaf._offset | 0x80000000)
+                else:
+                    pack_into('<II', data, tbl_offs, id, desc_offs)
+                    resdata = leaf.get_data()
+                    pack_into('<IIII', data, desc_offs, data_addr, len(resdata), leaf.code_page, 0)
+                    data += resdata
+                    desc_offs += 16
+                    data_addr += len(resdata)
+                    align = len(resdata) & 3
+                    if align:
+                        data += bytearray(4 - align)
+                        data_addr += 4 - align
+                tbl_offs += 8
+
+        flags = 0x40000040 # readable, contains initialized data
+        section = self.add_section('.rsrc', flags, data)
+        self.res_rva = RVASize(section.vaddr, section.vsize)
+
+    def write_changes(self):
+        """ Assuming the file was opened in read-write mode, writes back the
+        changes made via this class to the .exe file. """
+
+        fp = self.fp
+        # Read position of header.
+        fp.seek(0x3c)
+        offset, = unpack('<I', fp.read(4))
+
+        fp.seek(offset)
+        if fp.read(4) != b'PE\0\0':
+            raise ValueError("Invalid PE file.")
+
+        # Sync read/write pointer.  Necessary before write.  Bug in Python?
+        fp.seek(fp.tell())
+
+        # Rewrite the first part of the COFF header.
+        timdat = int(time.time())
+        fp.write(pack('<HHI', self.machine, len(self.sections), timdat))
+
+        # Write calculated init and uninitialised sizes to the opthdr.
+        fp.seek(16, 1)
+        fp.write(pack('<III', self.code_size, self.initialized_size, self.uninitialized_size))
+
+        # Same for the image and header size.
+        fp.seek(40, 1)
+        fp.write(pack('<II', self.image_size, self.header_size))
+
+        # Write the modified RVA table.
+        fp.seek(self.rva_offset)
+        numrvas, = unpack('<I', fp.read(4))
+        assert numrvas >= 3
+
+        fp.seek(self.rva_offset + 4)
+        if numrvas >= 1:
+            fp.write(pack('<II', *self.exp_rva))
+        if numrvas >= 2:
+            fp.write(pack('<II', *self.imp_rva))
+        if numrvas >= 3:
+            fp.write(pack('<II', *self.res_rva))
+
+        # Skip the rest of the tables.
+        if numrvas >= 4:
+            fp.seek((numrvas - 3) * 8, 1)
+
+        # Write the modified section headers.
+        for section in self.sections:
+            section.write_header(fp)
+            assert fp.tell() <= self.header_size
+
+        # Write the section data of modified sections.
+        for section in self.sections:
+            if not section.modified:
+                continue
+
+            fp.seek(section.offset)
+            size = min(section.vsize, section.size)
+            fp.write(self.vmem[section.vaddr:section.vaddr+size])
+
+            pad = section.size - size
+            assert pad >= 0
+            if pad > 0:
+                fp.write(bytearray(pad))
+
+            section.modified = False

+ 1 - 1
direct/src/showutil/pfreeze.py → direct/src/dist/pfreeze.py

@@ -59,7 +59,7 @@ Options::
 import getopt
 import sys
 import os
-from direct.showutil import FreezeTool
+from . import FreezeTool
 
 def usage(code, msg = ''):
     if __doc__:

+ 1 - 1
direct/src/distributed/cConnectionRepository.cxx

@@ -891,7 +891,7 @@ describe_message(std::ostream &out, const string &prefix,
                  const Datagram &dg) const {
   DCPacker packer;
 
-  packer.set_unpack_data(dg.get_message());
+  packer.set_unpack_data((const char *)dg.get_data(), dg.get_length(), false);
   CHANNEL_TYPE do_id;
   int msg_type;
   bool is_update = false;

+ 22 - 19
direct/src/filter/CommonFilters.py

@@ -16,6 +16,13 @@ clunky approach.  - Josh
 """
 
 from .FilterManager import FilterManager
+from .filterBloomI import BLOOM_I
+from .filterBloomX import BLOOM_X
+from .filterBloomY import BLOOM_Y
+from .filterBlurX import BLUR_X
+from .filterBlurY import BLUR_Y
+from .filterCopy import COPY
+from .filterDown4 import DOWN_4
 from panda3d.core import LVecBase4, LPoint2
 from panda3d.core import Filename
 from panda3d.core import AuxBitplaneAttrib
@@ -102,12 +109,6 @@ class CommonFilters:
         self.task = None
         self.cleanup()
 
-    def loadShader(self, name):
-        fn = os.path.join(os.path.abspath(os.path.dirname(__file__)), name)
-        fn = Filename.fromOsSpecific(fn)
-        fn.makeTrueCase()
-        return Shader.load(fn)
-
     def cleanup(self):
         self.manager.cleanup()
         self.textures = {}
@@ -187,9 +188,9 @@ class CommonFilters:
                 self.blur.append(self.manager.renderQuadInto("filter-blur0", colortex=blur0,div=2))
                 self.blur.append(self.manager.renderQuadInto("filter-blur1", colortex=blur1))
                 self.blur[0].setShaderInput("src", self.textures["color"])
-                self.blur[0].setShader(self.loadShader("filter-blurx.sha"))
+                self.blur[0].setShader(Shader.make(BLUR_X, Shader.SL_Cg))
                 self.blur[1].setShaderInput("src", blur0)
-                self.blur[1].setShader(self.loadShader("filter-blury.sha"))
+                self.blur[1].setShader(Shader.make(BLUR_Y, Shader.SL_Cg))
 
             if ("AmbientOcclusion" in configuration):
                 ssao0=self.textures["ssao0"]
@@ -203,9 +204,9 @@ class CommonFilters:
                 self.ssao[0].setShaderInput("random", loader.loadTexture("maps/random.rgb"))
                 self.ssao[0].setShader(Shader.make(SSAO_BODY % configuration["AmbientOcclusion"].numsamples, Shader.SL_Cg))
                 self.ssao[1].setShaderInput("src", ssao0)
-                self.ssao[1].setShader(self.loadShader("filter-blurx.sha"))
+                self.ssao[1].setShader(Shader.make(BLUR_X, Shader.SL_Cg))
                 self.ssao[2].setShaderInput("src", ssao1)
-                self.ssao[2].setShader(self.loadShader("filter-blury.sha"))
+                self.ssao[2].setShader(Shader.make(BLUR_Y, Shader.SL_Cg))
 
             if ("Bloom" in configuration):
                 bloomconf = configuration["Bloom"]
@@ -215,25 +216,28 @@ class CommonFilters:
                 bloom3=self.textures["bloom3"]
                 if (bloomconf.size == "large"):
                     scale=8
-                    downsampler="filter-down4"
+                    downsamplerName="filter-down4"
+                    downsampler=DOWN_4
                 elif (bloomconf.size == "medium"):
                     scale=4
-                    downsampler="filter-copy"
+                    downsamplerName="filter-copy"
+                    downsampler=COPY
                 else:
                     scale=2
-                    downsampler="filter-copy"
+                    downsamplerName="filter-copy"
+                    downsampler=COPY
                 self.bloom.append(self.manager.renderQuadInto("filter-bloomi", colortex=bloom0, div=2,     align=scale))
-                self.bloom.append(self.manager.renderQuadInto(downsampler, colortex=bloom1, div=scale, align=scale))
+                self.bloom.append(self.manager.renderQuadInto(downsamplerName, colortex=bloom1, div=scale, align=scale))
                 self.bloom.append(self.manager.renderQuadInto("filter-bloomx", colortex=bloom2, div=scale, align=scale))
                 self.bloom.append(self.manager.renderQuadInto("filter-bloomy", colortex=bloom3, div=scale, align=scale))
                 self.bloom[0].setShaderInput("src", self.textures["color"])
-                self.bloom[0].setShader(self.loadShader("filter-bloomi.sha"))
+                self.bloom[0].setShader(Shader.make(BLOOM_I, Shader.SL_Cg))
                 self.bloom[1].setShaderInput("src", bloom0)
-                self.bloom[1].setShader(self.loadShader(downsampler + ".sha"))
+                self.bloom[1].setShader(Shader.make(downsampler, Shader.SL_Cg))
                 self.bloom[2].setShaderInput("src", bloom1)
-                self.bloom[2].setShader(self.loadShader("filter-bloomx.sha"))
+                self.bloom[2].setShader(Shader.make(BLOOM_X, Shader.SL_Cg))
                 self.bloom[3].setShaderInput("src", bloom2)
-                self.bloom[3].setShader(self.loadShader("filter-bloomy.sha"))
+                self.bloom[3].setShader(Shader.make(BLOOM_Y, Shader.SL_Cg))
 
             texcoords = {}
             texcoordPadding = {}
@@ -558,7 +562,6 @@ class CommonFilters:
     set_cartoon_ink = setCartoonInk
     del_bloom = delBloom
     del_ambient_occlusion = delAmbientOcclusion
-    load_shader = loadShader
     set_blur_sharpen = setBlurSharpen
     del_blur_sharpen = delBlurSharpen
     del_volumetric_lighting = delVolumetricLighting

+ 3 - 1
direct/src/filter/filter-bloomi.sha → direct/src/filter/filterBloomI.py

@@ -1,3 +1,4 @@
+BLOOM_I = """
 //Cg
 //
 // blend.rgb
@@ -23,7 +24,7 @@
 // trigger.y
 //
 //   Must be equal to (1.0/(maxtrigger-mintrigger)) where
-//   
+//
 //   mintrigger is the minimum brightness to trigger a bloom,
 //   and maxtrigger is the brightness at which the bloom
 //   reaches maximum intensity.
@@ -83,3 +84,4 @@ void fshader(float2 l_texcoordNW : TEXCOORD0,
   o_color = (colorNW + colorNE + colorSW + colorSE) * 0.25;
 }
 
+"""

+ 2 - 0
direct/src/filter/filter-bloomx.sha → direct/src/filter/filterBloomX.py

@@ -1,3 +1,4 @@
+BLOOM_X = """
 //Cg
 
 void vshader(float4 vtx_position : POSITION,
@@ -46,3 +47,4 @@ void fshader(float4 l_texcoord0 : TEXCOORD0,
   color +=  50 * tex2D(k_src, l_texcoord2.zw);
   o_color = color / 1200.0;
 }
+"""

+ 2 - 0
direct/src/filter/filter-bloomy.sha → direct/src/filter/filterBloomY.py

@@ -1,3 +1,4 @@
+BLOOM_Y = """
 //Cg
 
 void vshader(float4 vtx_position : POSITION,
@@ -37,3 +38,4 @@ void fshader(float4 l_texcoord0 : TEXCOORD0,
   o_color = color / 1200.0;
   o_color = o_color * k_intensity;
 }
+"""

+ 2 - 0
direct/src/filter/filter-blurx.sha → direct/src/filter/filterBlurX.py

@@ -1,3 +1,4 @@
+BLUR_X = """
 //Cg
 //
 //Cg profile arbvp1 arbfp1
@@ -33,3 +34,4 @@ void fshader(float2 l_texcoord0 : TEXCOORD0,
   o_color.w = 1;
 }
 
+"""

+ 2 - 1
direct/src/filter/filter-blury.sha → direct/src/filter/filterBlurY.py

@@ -1,3 +1,4 @@
+BLUR_Y = """
 //Cg
 //
 //Cg profile arbvp1 arbfp1
@@ -32,4 +33,4 @@ void fshader(float2 l_texcoord0 : TEXCOORD0,
   o_color /= 7;
   o_color.w = 1;
 }
-
+"""

+ 2 - 1
direct/src/filter/filter-copy.sha → direct/src/filter/filterCopy.py

@@ -1,3 +1,4 @@
+COPY = """
 //Cg
 
 
@@ -17,4 +18,4 @@ void fshader(float2 l_texcoord : TEXCOORD0,
 {
   o_color = tex2D(k_src, l_texcoord);
 }
-
+"""

+ 2 - 1
direct/src/filter/filter-down4.sha → direct/src/filter/filterDown4.py

@@ -1,3 +1,4 @@
+DOWN_4 = """
 //Cg
 
 void vshader(float4 vtx_position : POSITION,
@@ -30,4 +31,4 @@ void fshader(float2 l_texcoordNW : TEXCOORD0,
   float4 colorSE = tex2D(k_src, l_texcoordSE);
   o_color = (colorNW + colorNE + colorSW + colorSE);
 }
-
+"""

+ 1 - 1
direct/src/p3d/Packager.py

@@ -19,7 +19,7 @@ from direct.p3d.SeqValue import SeqValue
 from direct.p3d.HostInfo import HostInfo
 from direct.showbase import Loader
 from direct.showbase import AppRunnerGlobal
-from direct.showutil import FreezeTool
+from direct.dist import FreezeTool
 from direct.directnotify.DirectNotifyGlobal import *
 
 vfs = VirtualFileSystem.getGlobalPtr()

+ 0 - 4
direct/src/p3d/panda3d.pdef

@@ -71,10 +71,6 @@ class panda3d(package):
     if sys.version_info < (3, 0):
         module('encodings.string_escape')
 
-    # Pick up the shader files that appear in direct/src/filter.
-    import direct
-    file(Filename(direct.__path__[0], 'filter/*.sha'), newDir = 'direct/filter')
-
     # pandac.PandaModules pulls in other Panda3D libs automatically.
     # Exclude these Panda3D libs; they are big and many applications don't
     # use them.  We define them as separate, optional packages, below,

+ 63 - 0
direct/src/showbase/ShowBase.py

@@ -170,6 +170,7 @@ class ShowBase(DirectObject.DirectObject):
         self.trackball = None
         self.texmem = None
         self.showVertices = None
+        self.deviceButtonThrowers = []
 
         ## This is a NodePath pointing to the Camera object set up for the 3D scene.
         ## This is usually a child of self.camera.
@@ -294,6 +295,7 @@ class ShowBase(DirectObject.DirectObject):
         ## The global job manager, as imported from JobManagerGlobal.
         self.jobMgr = jobMgr
 
+
         ## Particle manager
         self.particleMgr = None
         self.particleMgrEnabled = 0
@@ -303,6 +305,11 @@ class ShowBase(DirectObject.DirectObject):
         self.physicsMgrEnabled = 0
         self.physicsMgrAngular = 0
 
+        ## This is the global input device manager, which keeps track of
+        ## connected input devices.
+        self.devices = InputDeviceManager.getGlobalPtr()
+        self.__inputDeviceNodes = {}
+
         self.createStats()
 
         self.AppHasAudioFocus = 1
@@ -1670,6 +1677,57 @@ class ShowBase(DirectObject.DirectObject):
         return self.mouseWatcherNode.getModifierButtons().isDown(
             KeyboardButton.meta())
 
+    def attachInputDevice(self, device, prefix=None):
+        """
+        This function attaches an input device to the data graph, which will
+        cause the device to be polled and generate events.  If a prefix is
+        given and not None, it is used to prefix events generated by this
+        device, separated by a hyphen.
+
+        If you call this, you should consider calling detachInputDevice when
+        you are done with the device or when it is disconnected.
+        """
+
+        # Protect against the same device being attached multiple times.
+        assert device not in self.__inputDeviceNodes
+
+        idn = self.dataRoot.attachNewNode(InputDeviceNode(device, device.name))
+
+        # Setup the button thrower to generate events for the device.
+        bt = idn.attachNewNode(ButtonThrower(device.name))
+        if prefix is not None:
+            bt.node().setPrefix(prefix + '-')
+
+        assert self.notify.debug("Attached input device {0} with prefix {1}".format(device, prefix))
+        self.__inputDeviceNodes[device] = idn
+        self.deviceButtonThrowers.append(bt)
+
+    def detachInputDevice(self, device):
+        """
+        This should be called after attaching an input device using
+        attachInputDevice and the device is disconnected or you no longer wish
+        to keep polling this device for events.
+
+        You do not strictly need to call this if you expect the device to be
+        reconnected (but be careful that you don't reattach it).
+        """
+
+        if device not in self.__inputDeviceNodes:
+            assert device in self.__inputDeviceNodes
+            return
+
+        assert self.notify.debug("Detached device {0}".format(device.name))
+
+        # Remove the ButtonThrower from the deviceButtonThrowers list.
+        idn = self.__inputDeviceNodes[device]
+        for bt in self.deviceButtonThrowers:
+            if idn.isAncestorOf(bt):
+                self.deviceButtonThrowers.remove(bt)
+                break
+
+        idn.removeNode()
+        del self.__inputDeviceNodes[device]
+
     def addAngularIntegrator(self):
         if not self.physicsMgrAngular:
             physics = importlib.import_module('panda3d.physics')
@@ -1859,6 +1917,9 @@ class ShowBase(DirectObject.DirectObject):
         return Task.cont
 
     def __dataLoop(self, state):
+        # Check if there were newly connected devices.
+        self.devices.update()
+
         # traverse the data graph.  This reads all the control
         # inputs (from the mouse and keyboard, for instance) and also
         # directly acts upon them (for instance, to move the avatar).
@@ -3074,6 +3135,8 @@ class ShowBase(DirectObject.DirectObject):
     setup_mouse = setupMouse
     setup_mouse_cb = setupMouseCB
     enable_software_mouse_pointer = enableSoftwareMousePointer
+    detach_input_device = detachInputDevice
+    attach_input_device = attachInputDevice
     add_angular_integrator = addAngularIntegrator
     enable_particles = enableParticles
     disable_particles = disableParticles

+ 7 - 10
direct/src/showbase/Transitions.py

@@ -89,6 +89,8 @@ class Transitions:
             self.fade.setBin('unsorted', 0)
             self.fade.setColor(0,0,0,0)
 
+        self.fade.setScale(max(base.a2dRight, base.a2dTop))
+
     def getFadeInIval(self, t=0.5, finishIval=None, blendType='noBlend'):
         """
         Returns an interval without starting it.  This is particularly useful in
@@ -97,8 +99,7 @@ class Transitions:
         #self.noTransitions() masad: this creates a one frame pop, is it necessary?
         self.loadFade()
 
-        parent = aspect2d if self.fadeModel else render2d
-        transitionIval = Sequence(Func(self.fade.reparentTo, parent, DGG.FADE_SORT_INDEX),
+        transitionIval = Sequence(Func(self.fade.reparentTo, aspect2d, DGG.FADE_SORT_INDEX),
                                   Func(self.fade.showThrough),  # in case aspect2d is hidden for some reason
                                   self.lerpFunc(self.fade, t,
                                                 self.alphaOff,
@@ -120,8 +121,7 @@ class Transitions:
         self.noTransitions()
         self.loadFade()
 
-        parent = aspect2d if self.fadeModel else render2d
-        transitionIval = Sequence(Func(self.fade.reparentTo, parent, DGG.FADE_SORT_INDEX),
+        transitionIval = Sequence(Func(self.fade.reparentTo, aspect2d, DGG.FADE_SORT_INDEX),
                                   Func(self.fade.showThrough),  # in case aspect2d is hidden for some reason
                                   self.lerpFunc(self.fade, t,
                                                 self.alphaOn,
@@ -181,8 +181,7 @@ class Transitions:
             self.noTransitions()
             self.loadFade()
 
-            parent = aspect2d if self.fadeModel else render2d
-            self.fade.reparentTo(parent, DGG.FADE_SORT_INDEX)
+            self.fade.reparentTo(aspect2d, DGG.FADE_SORT_INDEX)
             self.fade.setColor(self.alphaOn)
         elif ConfigVariableBool('no-loading-screen', False):
             if finishIval:
@@ -215,8 +214,7 @@ class Transitions:
         self.noTransitions()
         self.loadFade()
 
-        parent = aspect2d if self.fadeModel else render2d
-        self.fade.reparentTo(parent, DGG.FADE_SORT_INDEX)
+        self.fade.reparentTo(aspect2d, DGG.FADE_SORT_INDEX)
         self.fade.setColor(self.alphaOn[0],
                            self.alphaOn[1],
                            self.alphaOn[2],
@@ -232,8 +230,7 @@ class Transitions:
         self.noTransitions()
         self.loadFade()
 
-        parent = aspect2d if self.fadeModel else render2d
-        self.fade.reparentTo(parent, DGG.FADE_SORT_INDEX)
+        self.fade.reparentTo(aspect2d, DGG.FADE_SORT_INDEX)
         self.fade.setColor(color)
 
     def noFade(self):

+ 0 - 10
direct/src/stdpy/thread.py

@@ -277,13 +277,6 @@ class _local(object):
         d = _get_thread_locals(core.Thread.getCurrentThread(), id(self))
         d[key] = value
 
-##     def __getattr__(self, key):
-##         d = _get_thread_locals(core.Thread.getCurrentThread(), id(self))
-##         try:
-##             return d[key]
-##         except KeyError:
-##             raise AttributeError
-
     def __getattribute__(self, key):
         d = _get_thread_locals(core.Thread.getCurrentThread(), id(self))
         if key == '__dict__':
@@ -292,6 +285,3 @@ class _local(object):
             return d[key]
         except KeyError:
             return object.__getattribute__(self, key)
-
-
-

+ 0 - 1
doc/INSTALL

@@ -194,7 +194,6 @@ it will show you the available command-line options:
   --use-wx          --no-wx        (enable/disable use of WX)
   --use-fltk        --no-fltk      (enable/disable use of FLTK)
   --use-rocket      --no-rocket    (enable/disable use of ROCKET)
-  --use-awesomium   --no-awesomium (enable/disable use of AWESOMIUM)
   --use-carbon      --no-carbon    (enable/disable use of CARBON)
   --use-cocoa       --no-cocoa     (enable/disable use of COCOA)
   --use-x11         --no-x11       (enable/disable use of X11)

+ 148 - 0
doc/ReleaseNotes

@@ -1,3 +1,151 @@
+------------------------  RELEASE 1.10.0  -----------------------
+
+This is a major release with significant changes.  Please review the
+changes when upgrading.  The list below is by no means exhaustive, but
+should contain the most important changes.
+
+General
+* Experimental ability to build for Android
+* New input framework to natively support gamepads, joysticks, etc.
+* Multi-threaded render pipeline is a lot more stable now
+* New setuptools-based deployment pipeline
+* Improvements to mouselook smoothness
+* Cache is now at $XDG_CACHE_HOME/panda3d (~/.cache/panda3d), not ~/.panda3d
+* Addition of unit test suite
+* Many improvements to thread safety
+* Many performance improvements
+* Tons of bugfixes
+* Big style cleanup of C++ source code
+
+Python API
+* Complete support for Python 3
+* Support for coroutines and async/await
+* Property interfaces have been added for many settings
+* More flexible handling for keyboard arguments in C++ APIs
+* Python bindings are completely separated out of the C++ libraries.
+* Interrogate binding generator has many improvements.
+* Use of pandac.PandaModules is discouraged, use panda3d.core et al
+* Use of libRocket is discouraged due to lack of Python 3 support
+* Tasks are now sorted in addition order when lacking a sort value
+* Fixes iris/fade transitions for extreme aspect ratios
+* WeakNodePath is now exposed to Python
+* WindowProperties.size(x, y) deprecated; use WindowProperties(size=(x, y))
+* Calling bare run() is deprecated, use base.run() instead
+* downcastTo*() methods have been removed, they were already no-ops
+
+Rendering
+* Add new shader-based terrain rendering method (ShaderTerrainMesh)
+* The default ColorAttrib mode is now T_vertex
+* The ColorAttrib T_off mode now properly disables vertex colors entirely
+* Make handling of color attributes more consistent between renderers
+* Ability to create an OpenGL core profile context; set "gl-version 3 2"
+* Experimental support for reverse-Z rendering for best depth precision
+* sRGB framebuffers supported more widely
+* Support for infinite near/far clip in lens
+* Add some PBR material parameters to material class
+* Addition of more built-in GLSL shader inputs; see manual.
+* Add p3d_FragData[] GLSL output for MRT in GLSL 1.30
+* Add flag enabling vertex shader control over point size
+* Support signed ints and double-precision floats in vertex data with GLSL
+* Support unsigned 11/10/10-bit floating-point textures and vertex data
+* Support for SSBOs via ShaderBuffer class
+* Support OpenGL FBO buffers without any attachments
+* Support passing uint variables to GLSL shader
+* Allow rendering objects with empty vertex data (for vertex pulling)
+* Add LogicOpAttrib, for supporting logical operator blending
+* Improvements to OpenGL ES support
+* Support for geometry with adjacency information
+* Change default alpha blending to improve blending rendered result
+* New method for obtaining native OpenGL texture object
+* Support windowless offscreen rendering on macOS
+* Panda resets OpenGL state better before and after draw callbacks
+* OpenGL renderer better supports debugging tools like apitrace
+* Support fixed-depth billboards, useful for 2D tags that don't change size
+
+Shader generator
+* Significant performance improvements
+* Support for point light shadows
+* Hardware skinning support
+* Changes to match fixed-function pipeline better
+* Fixes for normal vector normalization
+* Support multiple normal maps (uses Reoriented Normal Mapping)
+* Tracks modifications to materials and texture stages automatically
+
+Lighting
+* Allow specifying light color based on color temperature
+* Setting specular color of a light separately is deprecated
+* New GLSL inputs to make implementing lighting in shaders much easier
+* Add representation for sphere light and rectangle light
+* Efficiency improvements for passing light information to shader
+* Interocular distance for shadow cameras now always defaults to 0
+* Add low-level lighting module from RenderPipeline
+
+Textures
+* Support cube map arrays
+* Support buffer textures
+* Many more texture formats supported
+* BC4 and BC5 compression modes supported
+* Proper depth textures supported in DirectX 9 renderer
+* set_ram_image(_as) directly supports buffer protocol
+* TexturePeeker supports more formats and component types
+
+Text
+* Dramatic improvements to text rendering performance
+* Support for HarfBuzz for higher-quality text shaping and kerning
+* Support for right-to-left text
+* Support for signed-distance-field rendering in egg-mkfont
+
+Audio/video
+* The default unit for audio is now 1 meter for each Panda unit.
+* Native .flac loader
+* Support videos with alpha channel in ffmpeg
+* OpenAL stability improvements, especially on macOS
+* Support loading .opus files with libopusfile
+* Fix various memory leaks
+
+Physics / collisions
+* CollisionTube is renamed to CollisionCapsule.
+* Box-box collision test is improved to work well with the Pusher
+* More box tests for collision system: box-into-plane, box-into-poly
+* Capsule (tube) can be used as "from" shape into plane, sphere, capsule, box
+* Bullet objects are serializable to .bam files.
+* Bullet bindings are now thread safe.
+* Bullet debug drawer is more efficient; no longer inherits GeomNode.
+* Various fixes to bullet vehicle wheel synchronization
+* PhysX bindings are deprecated.
+
+Pipeline / loading
+* Support for Assimp library to load a broad variety of model formats
+* Ability to specify min-lod, max-lod, lod-bias in .egg file
+* Egg file materials support PBR-style material parameterization
+* Support loading more DDS files, including DX10-style ones
+* Add support for OpenEXR and HDR textures
+* Support line/point thickness in bam2egg
+* bam2egg no longer inserts a vestigial ModelNode at the top
+* bam2egg supports depth test, offset, cull bin attributes
+* Accept a .gz file wherever a .pz file is accepted
+* egg-palettize supports mirror and border-color wrap modes
+* More robust checks against memory corruptions when loading bad .bam files
+* Support for Maya 2017 and 2018
+* Support preprocessing GLSL shaders created with Shader.make
+
+Build
+* We now require using MSVC 2015 or 2017 to compile on Windows.
+* At least GCC 4.8 is now required.
+* With GCC/clang, enabling C++11 is now required.
+* Allow building with more recent ffmpeg versions
+* Support for old FFMpeg versions (before 1.1) dropped.
+* The ppremake build system has been removed.
+* Support for OpenSSL versions before 0.9.7 has been dropped.
+
+C++
+* Use of NULL is replaced with nullptr
+* WeakPointerTo now requires use of lock() method for thread safety
+* Mutex et al now satisfy C++11 Lockable constraints
+* Panda headers no longer contain `using namespace std;`
+* PN_int32 et al have been removed, use stdint.h types instead
+* The need to link with pystub and add Python include dirs is removed.
+
 ------------------------  RELEASE 1.9.4  ------------------------
 
 One of the bugfixes in the last 1.9.3 release introduced a regression,

Diferenças do arquivo suprimidas por serem muito extensas
+ 155 - 125
dtool/metalibs/dtoolconfig/pydtool.cxx


+ 58 - 2
dtool/src/cppparser/cppExpression.cxx

@@ -25,6 +25,7 @@
 #include "cppFunctionGroup.h"
 #include "cppFunctionType.h"
 #include "cppClosureType.h"
+#include "cppReferenceType.h"
 #include "cppStructType.h"
 #include "cppBison.h"
 #include "pdtoa.h"
@@ -260,12 +261,12 @@ CPPExpression(CPPIdentifier *ident, CPPScope *current_scope,
       _u._variable = inst;
       return;
     }
-    CPPFunctionGroup *fgroup = decl->as_function_group();
+    /*CPPFunctionGroup *fgroup = decl->as_function_group();
     if (fgroup != nullptr) {
       _type = T_function;
       _u._fgroup = fgroup;
       return;
-    }
+    }*/
   }
 
   _type = T_unknown_ident;
@@ -1232,6 +1233,61 @@ determine_type() const {
   return nullptr;  // Compiler kludge; can't get here.
 }
 
+/**
+ * Returns true if this is an lvalue expression.
+ */
+bool CPPExpression::
+is_lvalue() const {
+  switch (_type) {
+  case T_variable:
+  case T_function:
+  case T_unknown_ident:
+    return true;
+
+  case T_typecast:
+  case T_static_cast:
+  case T_dynamic_cast:
+  case T_const_cast:
+  case T_reinterpret_cast:
+    {
+      CPPReferenceType *ref_type = _u._typecast._to->as_reference_type();
+      return ref_type != nullptr && ref_type->_value_category == CPPReferenceType::VC_lvalue;
+    }
+
+  case T_unary_operation:
+    if (_u._op._operator == 'f') {
+      // A function returning an lvalue reference.
+      CPPType *return_type = determine_type();
+      if (return_type != nullptr) {
+        CPPReferenceType *ref_type = return_type->as_reference_type();
+        return ref_type != nullptr && ref_type->_value_category == CPPReferenceType::VC_lvalue;
+      }
+    }
+    return _u._op._operator == PLUSPLUS
+        || _u._op._operator == MINUSMINUS
+        || _u._op._operator == '*';
+
+  case T_binary_operation:
+    if (_u._op._operator == ',') {
+      CPPReferenceType *ref_type = _u._op._op2->as_reference_type();
+      return ref_type != nullptr && ref_type->_value_category == CPPReferenceType::VC_lvalue;
+    }
+    return (_u._op._operator == POINTSAT || _u._op._operator == ',');
+
+  case T_trinary_operation:
+    return _u._op._op2->is_lvalue() && _u._op._op3->is_lvalue();
+
+  case T_literal:
+  case T_raw_literal:
+    return true;
+
+  default:
+    break;
+  }
+
+  return false;
+}
+
 /**
  * Returns true if this declaration is an actual, factual declaration, or
  * false if some part of the declaration depends on a template parameter which

+ 1 - 0
dtool/src/cppparser/cppExpression.h

@@ -133,6 +133,7 @@ public:
 
   Result evaluate() const;
   CPPType *determine_type() const;
+  bool is_lvalue() const;
   bool is_tbd() const;
 
   virtual bool is_fully_specified() const;

+ 122 - 3
dtool/src/dtoolutil/globPattern.cxx

@@ -12,6 +12,7 @@
  */
 
 #include "globPattern.h"
+#include "string_utils.h"
 #include <ctype.h>
 
 using std::string;
@@ -199,9 +200,7 @@ r_match_files(const Filename &prefix, const string &suffix,
     next_glob = *this;
   }
 
-  vector_string::const_iterator fi;
-  for (fi = dir_files.begin(); fi != dir_files.end(); ++fi) {
-    const string &local_file = (*fi);
+  for (const string &local_file : dir_files) {
     if (_pattern[0] == '.' || (local_file.empty() || local_file[0] != '.')) {
       if (matches(local_file)) {
         // We have a match; continue.
@@ -227,6 +226,126 @@ r_match_files(const Filename &prefix, const string &suffix,
   return num_matched;
 }
 
+/**
+ * Treats the GlobPattern as a filename pattern, and returns true if the given
+ * filename matches the pattern.  Unlike matches(), this will not match slash
+ * characters for single asterisk characters, and it will ignore path
+ * components that only contain a dot.
+ */
+bool GlobPattern::
+matches_file(Filename candidate) const {
+  if (_pattern.empty()) {
+    // Special case.
+    return candidate.empty();
+  }
+
+  // Either both must be absolute, or both must be relative.
+  if ((_pattern[0] != '/') != candidate.is_local()) {
+    return false;
+  }
+
+  return r_matches_file(_pattern, candidate);
+}
+
+/**
+ * The recursive implementation of matches_file().
+ */
+bool GlobPattern::
+r_matches_file(const string &pattern, const Filename &candidate) const {
+  // Split off the next bit of pattern.
+  std::string next_pattern;
+  GlobPattern glob;
+  glob.set_case_sensitive(get_case_sensitive());
+  glob.set_nomatch_chars(get_nomatch_chars());
+
+  bool pattern_end;
+  size_t slash = pattern.find('/');
+  if (slash == string::npos) {
+    glob.set_pattern(pattern);
+    pattern_end = true;
+  } else {
+    glob.set_pattern(pattern.substr(0, slash));
+    next_pattern = pattern.substr(slash + 1);
+    pattern_end = false;
+
+    if (slash == 0 || (slash == 1 && pattern[0] == '.')) {
+      // Ignore // and /./ in patterns
+      return r_matches_file(next_pattern, candidate);
+    }
+  }
+
+  // Also split off the next component in the candidate filename.
+  std::string part;
+  Filename next_candidate;
+
+  bool candidate_end;
+  size_t fn_slash = ((const std::string &)candidate).find('/');
+  if (fn_slash == string::npos) {
+    part = candidate;
+    candidate_end = true;
+  } else {
+    part = candidate.substr(0, fn_slash);
+    next_candidate = candidate.substr(fn_slash + 1);
+    candidate_end = false;
+
+    // Ignore // and /./ in filenames.
+    if (fn_slash == 0 || part == ".") {
+      return r_matches_file(pattern, next_candidate);
+    }
+  }
+
+  // Now check if the current part matches the current pattern.
+  bool part_matches;
+  if (glob.get_pattern() == "**") {
+    // This matches any number of parts.
+    if (pattern_end) {
+      // We might as well stop checking here; it matches whatever might come.
+      return true;
+    }
+    // We branch out to three options: either we match nothing, we match this
+    // part only, or we match this part and maybe more.
+    return r_matches_file(next_pattern, candidate)
+        || (!candidate_end && r_matches_file(next_pattern, next_candidate))
+        || (!candidate_end && r_matches_file(pattern, next_candidate));
+  }
+  else if (glob.get_pattern() == "*" && _nomatch_chars.empty()) {
+    // Matches any part (faster version of below)
+    part_matches = true;
+  }
+  else if ((glob.get_pattern() == "." && part.empty())
+        || (glob.get_pattern().empty() && part == ".")) {
+    // So that /path/. matches /path/, and vice versa.
+    part_matches = true;
+  }
+  else if (glob.has_glob_characters()) {
+    part_matches = glob.matches(part);
+  }
+  else if (get_case_sensitive()) {
+    part_matches = (part == glob.get_pattern());
+  }
+  else {
+    part_matches = (cmp_nocase(part, glob.get_pattern()) == 0);
+  }
+
+  if (!part_matches) {
+    // It doesn't match, so we end our search here.
+    return false;
+  }
+
+  if (candidate_end && pattern_end) {
+    // We've reached the end of both candidate and pattern, so it matches.
+    return true;
+  }
+
+  if (pattern_end != candidate_end) {
+    // One of them has ended, but the other hasn't, so it's not a match.
+    return false;
+  }
+
+  // It matches; move on to the next part.
+  return r_matches_file(next_pattern, next_candidate);
+}
+
 /**
  * The recursive implementation of matches().  This returns true if the
  * pattern substring [pi, pend) matches the candidate substring [ci, cend),

+ 2 - 0
dtool/src/dtoolutil/globPattern.h

@@ -52,6 +52,7 @@ PUBLISHED:
   MAKE_PROPERTY(nomatch_chars, get_nomatch_chars, set_nomatch_chars);
 
   INLINE bool matches(const std::string &candidate) const;
+  bool matches_file(Filename candidate) const;
 
   INLINE void output(std::ostream &out) const;
 
@@ -74,6 +75,7 @@ private:
 
   int r_match_files(const Filename &prefix, const std::string &suffix,
                     vector_string &results, const Filename &cwd);
+  bool r_matches_file(const std::string &suffix, const Filename &candidate) const;
 
   std::string _pattern;
   bool _case_sensitive;

+ 161 - 161
dtool/src/interrogate/interfaceMakerPythonNative.cxx

@@ -4078,13 +4078,9 @@ write_coerce_constructor(ostream &out, Object *obj, bool is_const) {
 
 /**
  * Special case optimization: if the last map is a subset of the map before
- * it, and the last parameter is only a simple parameter type (that we have
- * special default argument handling for), we can merge the cases.  When this
- * happens, we can make use of a special feature of PyArg_ParseTuple for
- * handling of these last few default arguments.  This doesn't work well for
- * all types of default expressions, though, hence the need for this elaborate
- * checking mechanism down here, which goes in parallel with the actual
- * optional arg handling logic in write_function_instance.
+ * it, we can merge the cases.  When this happens, we can make use of a
+ * special feature of PyArg_ParseTuple for handling of these last few default
+ * arguments.
  *
  * This isn't just to help reduce the amount of generated code; it also
  * enables arbitrary selection of keyword arguments for many functions, ie.
@@ -4095,14 +4091,6 @@ write_coerce_constructor(ostream &out, Object *obj, bool is_const) {
  * Thanks to this mechanism, we can call it like so:
  *
  * func(c=True, d=".")
- *
- * The return value is the minimum of the number of maximum arguments.
- *
- * Sorry, let me try that again: it returns the largest number of arguments
- * for which the overloads will be separated out rather than handled via the
- * special default handling mechanism.  Or something.
- *
- * Please don't hate me.
  */
 int InterfaceMakerPythonNative::
 collapse_default_remaps(std::map<int, std::set<FunctionRemap *> > &map_sets,
@@ -4118,70 +4106,8 @@ collapse_default_remaps(std::map<int, std::set<FunctionRemap *> > &map_sets,
     if (std::includes(rmi_next->second.begin(), rmi_next->second.end(),
                       rmi->second.begin(), rmi->second.end())) {
 
-      // Check if the nth argument is something we can easily create a default
-      // for.
-      std::set<FunctionRemap *>::iterator sii;
-      for (sii = rmi->second.begin(); sii != rmi->second.end(); ++sii) {
-        FunctionRemap *remap = (*sii);
-        size_t pn = (size_t)rmi->first;
-        if (!remap->_has_this || remap->_type == FunctionRemap::T_constructor) {
-          --pn;
-        }
-        nassertd(pn < remap->_parameters.size()) goto abort_iteration;
-
-        ParameterRemap *param = remap->_parameters[pn]._remap;
-        CPPType *type = param->get_new_type();
-
-        if (param->new_type_is_atomic_string()) {
-          CPPType *orig_type = param->get_orig_type();
-          if (TypeManager::is_char_pointer(orig_type)) {
-          } else if (TypeManager::is_wchar_pointer(orig_type)) {
-            goto abort_iteration;
-          } else if (TypeManager::is_wstring(orig_type)) {
-            goto abort_iteration;
-          } else if (TypeManager::is_const_ptr_to_basic_string_wchar(orig_type)) {
-            goto abort_iteration;
-          } else {
-            // Regular strings are OK if the default argument is a string
-            // literal or the default string constructor, since those are
-            // trivial to handle.  This actually covers almost all of the
-            // cases of default string args.
-            CPPExpression::Type expr_type = param->get_default_value()->_type;
-            if (expr_type != CPPExpression::T_default_construct &&
-                expr_type != CPPExpression::T_string) {
-              goto abort_iteration;
-            }
-          }
-        } else if (TypeManager::is_integer(type)) {
-        } else if (TypeManager::is_float(type)) {
-        } else if (TypeManager::is_const_char_pointer(type)) {
-        } else if (TypeManager::is_pointer_to_PyTypeObject(type)) {
-        } else if (TypeManager::is_pointer_to_PyStringObject(type)) {
-        } else if (TypeManager::is_pointer_to_PyUnicodeObject(type)) {
-        } else if (TypeManager::is_pointer_to_PyObject(type)) {
-        } else if (TypeManager::is_pointer_to_Py_buffer(type)) {
-          goto abort_iteration;
-        } else if (TypeManager::is_pointer_to_simple(type)) {
-          goto abort_iteration;
-        } else if (TypeManager::is_pointer(type)) {
-          // I'm allowing other pointer types, but only if the expression
-          // happens to evaluate to a numeric constant (which will likely only
-          // be NULL). There are too many issues to resolve right now with
-          // allowing more complex default expressions, including issues in
-          // the C++ parser (but the reader is welcome to give it a try!)
-          CPPExpression::Result res = param->get_default_value()->evaluate();
-          if (res._type != CPPExpression::RT_integer &&
-              res._type != CPPExpression::RT_pointer) {
-            goto abort_iteration;
-          }
-        } else {
-          goto abort_iteration;
-        }
-      }
-
       // rmi_next has a superset of the remaps in rmi, and we are going to
-      // erase rmi_next, so put all the remaps in rmi.  rmi->second =
-      // rmi_next->second;
+      // erase rmi_next, so put all the remaps in rmi.
 
       max_required_args = rmi_next->first;
       rmi = rmi_next;
@@ -4191,7 +4117,6 @@ collapse_default_remaps(std::map<int, std::set<FunctionRemap *> > &map_sets,
     }
   }
 
-abort_iteration:
   // Now erase the other remap sets.  Reverse iterators are weird, we first
   // need to get forward iterators and decrement them by one.
   std::map<int, std::set<FunctionRemap *> >::iterator erase_begin, erase_end;
@@ -4738,6 +4663,7 @@ write_function_instance(ostream &out, FunctionRemap *remap,
       "(" + orig_type->get_local_name(&parser) + ")" + param_name;
 
     string default_expr;
+    const char *null_assign = "";
 
     if (is_optional) {
       // If this is an optional argument, PyArg_ParseTuple will leave the
@@ -4747,6 +4673,7 @@ write_function_instance(ostream &out, FunctionRemap *remap,
       default_expr_str << " = ";
       default_value->output(default_expr_str, 0, &parser, false);
       default_expr = default_expr_str.str();
+      null_assign = " = nullptr";
 
       // We should only ever have to consider optional arguments for functions
       // taking a variable number of arguments.
@@ -4776,23 +4703,40 @@ write_function_instance(ostream &out, FunctionRemap *remap,
         expected_params += "str";
 
       } else if (TypeManager::is_wchar_pointer(orig_type)) {
-        indent(out, indent_level) << "#if PY_VERSION_HEX >= 0x03020000\n";
-        indent(out, indent_level) << "PyObject *" << param_name << ";\n";
-        indent(out, indent_level) << "#else\n";
-        indent(out, indent_level) << "PyUnicodeObject *" << param_name << ";\n";
-        indent(out, indent_level) << "#endif\n";
+        out << "#if PY_VERSION_HEX >= 0x03020000\n";
+        indent(out, indent_level) << "PyObject *" << param_name << null_assign << ";\n";
+        out << "#else\n";
+        indent(out, indent_level) << "PyUnicodeObject *" << param_name << null_assign << ";\n";
+        out << "#endif\n";
         format_specifiers += "U";
         parameter_list += ", &" + param_name;
 
-        extra_convert
-          << "#if PY_VERSION_HEX >= 0x03030000\n"
-          << "wchar_t *" << param_name << "_str = PyUnicode_AsWideCharString(" << param_name << ", nullptr);\n"
-          << "#else"
-          << "Py_ssize_t " << param_name << "_len = PyUnicode_GET_SIZE(" << param_name << ");\n"
-          << "wchar_t *" << param_name << "_str = (wchar_t *)alloca(sizeof(wchar_t) * (" + param_name + "_len + 1));\n"
-          << "PyUnicode_AsWideChar(" << param_name << ", " << param_name << "_str, " << param_name << "_len);\n"
-          << param_name << "_str[" << param_name << "_len] = 0;\n"
-          << "#endif\n";
+        if (is_optional) {
+          extra_convert
+            << "wchar_t *" << param_name << "_str;\n"
+            << "if (" << param_name << " != nullptr) {\n"
+            << "#if PY_VERSION_HEX >= 0x03030000\n"
+            << "  " << param_name << "_str = PyUnicode_AsWideCharString(" << param_name << ", nullptr);\n"
+            << "#else"
+            << "Py_ssize_t " << param_name << "_len = PyUnicode_GET_SIZE(" << param_name << ");\n"
+            << "  " << param_name << "_str = (wchar_t *)alloca(sizeof(wchar_t) * (" + param_name + "_len + 1));\n"
+            << "PyUnicode_AsWideChar(" << param_name << ", " << param_name << "_str, " << param_name << "_len);\n"
+            << param_name << "_str[" << param_name << "_len] = 0;\n"
+            << "#endif\n"
+            << "} else {\n"
+            << "  " << param_name << "_str" << default_expr << ";\n"
+            << "}\n";
+        } else {
+          extra_convert
+            << "#if PY_VERSION_HEX >= 0x03030000\n"
+            << "wchar_t *" << param_name << "_str = PyUnicode_AsWideCharString(" << param_name << ", nullptr);\n"
+            << "#else"
+            << "Py_ssize_t " << param_name << "_len = PyUnicode_GET_SIZE(" << param_name << ");\n"
+            << "wchar_t *" << param_name << "_str = (wchar_t *)alloca(sizeof(wchar_t) * (" + param_name + "_len + 1));\n"
+            << "PyUnicode_AsWideChar(" << param_name << ", " << param_name << "_str, " << param_name << "_len);\n"
+            << param_name << "_str[" << param_name << "_len] = 0;\n"
+            << "#endif\n";
+        }
 
         pexpr_string = param_name + "_str";
 
@@ -4803,56 +4747,48 @@ write_function_instance(ostream &out, FunctionRemap *remap,
 
         expected_params += "unicode";
 
-      } else if (TypeManager::is_wstring(orig_type)) {
-        indent(out, indent_level) << "#if PY_VERSION_HEX >= 0x03020000\n";
-        indent(out, indent_level) << "PyObject *" << param_name << ";\n";
-        indent(out, indent_level) << "#else\n";
-        indent(out, indent_level) << "PyUnicodeObject *" << param_name << ";\n";
-        indent(out, indent_level) << "#endif\n";
-        format_specifiers += "U";
-        parameter_list += ", &" + param_name;
-
-        extra_convert
-          << "#if PY_VERSION_HEX >= 0x03030000\n"
-          << "Py_ssize_t " << param_name << "_len;\n"
-          << "wchar_t *" << param_name << "_str = PyUnicode_AsWideCharString("
-          << param_name << ", &" << param_name << "_len);\n"
-          << "#else\n"
-          << "Py_ssize_t " << param_name << "_len = PyUnicode_GET_SIZE(" << param_name << ");\n"
-          << "wchar_t *" << param_name << "_str = (wchar_t *)alloca(sizeof(wchar_t) * (" + param_name + "_len + 1));\n"
-          << "PyUnicode_AsWideChar(" << param_name << ", " << param_name << "_str, " << param_name << "_len);\n"
-          << "#endif\n";
-
-        pexpr_string = param_name + "_str, " + param_name + "_len";
-
-        extra_cleanup
-          << "#if PY_VERSION_HEX >= 0x03030000\n"
-          << "PyMem_Free(" << param_name << "_str);\n"
-          << "#endif\n";
-
-        expected_params += "unicode";
-
-      } else if (TypeManager::is_const_ptr_to_basic_string_wchar(orig_type)) {
-        indent(out, indent_level) << "#if PY_VERSION_HEX >= 0x03020000\n";
-        indent(out, indent_level) << "PyObject *" << param_name << ";\n";
-        indent(out, indent_level) << "#else\n";
-        indent(out, indent_level) << "PyUnicodeObject *" << param_name << ";\n";
-        indent(out, indent_level) << "#endif\n";
+      } else if (TypeManager::is_wstring(orig_type) ||
+                 TypeManager::is_const_ptr_to_basic_string_wchar(orig_type)) {
+        out << "#if PY_VERSION_HEX >= 0x03020000\n";
+        indent(out, indent_level) << "PyObject *" << param_name << null_assign << ";\n";
+        out << "#else\n";
+        indent(out, indent_level) << "PyUnicodeObject *" << param_name << null_assign << ";\n";
+        out << "#endif\n";
         format_specifiers += "U";
         parameter_list += ", &" + param_name;
 
-        extra_convert
-          << "#if PY_VERSION_HEX >= 0x03030000\n"
-          << "Py_ssize_t " << param_name << "_len;\n"
-          << "wchar_t *" << param_name << "_str = PyUnicode_AsWideCharString("
-          << param_name << ", &" << param_name << "_len);\n"
-          << "#else\n"
-          << "Py_ssize_t " << param_name << "_len = PyUnicode_GET_SIZE(" << param_name << ");\n"
-          << "wchar_t *" << param_name << "_str = (wchar_t *)alloca(sizeof(wchar_t) * (" + param_name + "_len + 1));\n"
-          << "PyUnicode_AsWideChar(" << param_name << ", " << param_name << "_str, " << param_name << "_len);\n"
-          << "#endif\n";
-
-        pexpr_string = param_name + "_str, " + param_name + "_len";
+        if (is_optional) {
+          extra_convert
+            << "Py_ssize_t " << param_name << "_len;\n"
+            << "wchar_t *" << param_name << "_str;\n"
+            << "std::wstring " << param_name << "_wstr;\n"
+            << "if (" << param_name << " != nullptr) {\n"
+            << "#if PY_VERSION_HEX >= 0x03030000\n"
+            << "  " << param_name << "_str = PyUnicode_AsWideCharString("
+            << param_name << ", &" << param_name << "_len);\n"
+            << "#else\n"
+            << "  " << param_name << "_len = PyUnicode_GET_SIZE(" << param_name << ");\n"
+            << "  " << param_name << "_str = (wchar_t *)alloca(sizeof(wchar_t) * (" + param_name + "_len + 1));\n"
+            << "PyUnicode_AsWideChar(" << param_name << ", " << param_name << "_str, " << param_name << "_len);\n"
+            << "#endif\n"
+            << "  " << param_name << "_wstr.assign(" << param_name << "_str, " << param_name << "_len);\n"
+            << "} else {\n"
+            << "  " << param_name << "_wstr" << default_expr << ";\n"
+            << "}\n";
+          pexpr_string = "std::move(" + param_name + "_wstr)";
+        } else {
+          extra_convert
+            << "#if PY_VERSION_HEX >= 0x03030000\n"
+            << "Py_ssize_t " << param_name << "_len;\n"
+            << "wchar_t *" << param_name << "_str = PyUnicode_AsWideCharString("
+            << param_name << ", &" << param_name << "_len);\n"
+            << "#else\n"
+            << "Py_ssize_t " << param_name << "_len = PyUnicode_GET_SIZE(" << param_name << ");\n"
+            << "wchar_t *" << param_name << "_str = (wchar_t *)alloca(sizeof(wchar_t) * (" + param_name + "_len + 1));\n"
+            << "PyUnicode_AsWideChar(" << param_name << ", " << param_name << "_str, " << param_name << "_len);\n"
+            << "#endif\n";
+          pexpr_string = param_name + "_str, " + param_name + "_len";
+        }
 
         extra_cleanup
           << "#if PY_VERSION_HEX >= 0x03030000\n"
@@ -5019,11 +4955,11 @@ write_function_instance(ostream &out, FunctionRemap *remap,
       only_pyobjects = false;
 
     } else if (TypeManager::is_wchar(type)) {
-      indent(out, indent_level) << "#if PY_VERSION_HEX >= 0x03020000\n";
+      out << "#if PY_VERSION_HEX >= 0x03020000\n";
       indent(out, indent_level) << "PyObject *" << param_name << ";\n";
-      indent(out, indent_level) << "#else\n";
+      out << "#else\n";
       indent(out, indent_level) << "PyUnicodeObject *" << param_name << ";\n";
-      indent(out, indent_level) << "#endif\n";
+      out << "#endif\n";
       format_specifiers += "U";
       parameter_list += ", &" + param_name;
 
@@ -5348,17 +5284,36 @@ write_function_instance(ostream &out, FunctionRemap *remap,
       if (args_type == AT_single_arg) {
         param_name = "arg";
       } else {
-        indent(out, indent_level) << "PyObject *" << param_name << ";\n";
+        indent(out, indent_level) << "PyObject *" << param_name << null_assign << ";\n";
         format_specifiers += "O";
         parameter_list += ", &" + param_name;
       }
       indent(out, indent_level) << "Py_buffer " << param_name << "_view;\n";
 
-      extra_param_check << " && PyObject_GetBuffer("
-                        << param_name << ", &"
-                        << param_name << "_view, PyBUF_FULL) == 0";
-      pexpr_string = "&" + param_name + "_view";
-      extra_cleanup << "PyBuffer_Release(&" << param_name << "_view);\n";
+      if (is_optional) {
+        indent(out, indent_level) << "Py_buffer *" << param_name << "_viewp;\n";
+
+        extra_convert
+          << "bool " << param_name << "_success;\n"
+          << "if (" << param_name << " != nullptr) {\n"
+          << "  " << param_name << "_success = (PyObject_GetBuffer("
+          << param_name << ", &" << param_name << "_view, PyBUF_FULL) == 0);\n"
+          << "  " << param_name << "_viewp = &" << param_name << "_view;\n"
+          << "} else {\n"
+          << "  " << param_name << "_viewp" << default_expr << ";\n"
+          << "  " << param_name << "_success = true;\n"
+          << "}\n";
+
+        extra_param_check << " && " << param_name << "_success";
+        pexpr_string = param_name + "_viewp";
+        extra_cleanup << "if (" << param_name << " != nullptr) PyBuffer_Release(&" << param_name << "_view);\n";
+      } else {
+        extra_param_check << " && PyObject_GetBuffer("
+                          << param_name << ", &"
+                          << param_name << "_view, PyBUF_FULL) == 0";
+        pexpr_string = "&" + param_name + "_view";
+        extra_cleanup << "PyBuffer_Release(&" << param_name << "_view);\n";
+      }
       expected_params += "buffer";
       may_raise_typeerror = true;
       clear_error = true;
@@ -5367,7 +5322,7 @@ write_function_instance(ostream &out, FunctionRemap *remap,
       if (args_type == AT_single_arg) {
         param_name = "arg";
       } else {
-        indent(out, indent_level) << "PyObject *" << param_name << ";\n";
+        indent(out, indent_level) << "PyObject *" << param_name << null_assign << ";\n";
         format_specifiers += "O";
         parameter_list += ", &" + param_name;
       }
@@ -5496,18 +5451,15 @@ write_function_instance(ostream &out, FunctionRemap *remap,
       if (args_type == AT_single_arg) {
         param_name = "arg";
       } else {
-        indent(out, indent_level) << "PyObject *" << param_name;
-        if (is_optional) {
-          out << " = nullptr";
-        }
-        out << ";\n";
+        indent(out, indent_level) << "PyObject *" << param_name << null_assign << ";\n";
         format_specifiers += "O";
         parameter_list += ", &" + param_name;
       }
 
       // If the default value is NULL, we also accept a None value.
       bool maybe_none = false;
-      if (default_value != nullptr && (return_flags & RF_coerced) == 0) {
+      if (default_value != nullptr && (return_flags & RF_coerced) == 0 &&
+          TypeManager::is_pointer(orig_type)) {
         CPPExpression::Result res = param->get_default_value()->evaluate();
         if (res._type == CPPExpression::RT_integer ||
             res._type == CPPExpression::RT_pointer) {
@@ -5576,8 +5528,11 @@ write_function_instance(ostream &out, FunctionRemap *remap,
               << "if (" << param_name << " != nullptr && " << param_name << " != Py_None) {\n"
               << "  " << param_name << "_this";
           } else if (is_optional) {
+            if (TypeManager::is_pointer(orig_type)) {
+              extra_convert << default_expr;
+            }
             extra_convert
-              << default_expr << ";\n"
+              << ";\n"
               << "if (" << param_name << " != nullptr) {\n"
               << "  " << param_name << "_this";
           } else if (maybe_none) {
@@ -5590,7 +5545,13 @@ write_function_instance(ostream &out, FunctionRemap *remap,
           extra_convert << " = Dtool_Coerce_" + make_safe_name(class_name) +
             "(" + param_name + ", " + param_name + "_local);\n";
 
-          if (is_optional || maybe_none) {
+          if (is_optional && !TypeManager::is_pointer(orig_type)) {
+            extra_convert
+              << "} else {\n"
+              << "  " << param_name << "_local" << default_expr << ";\n"
+              << "  " << param_name << "_this = &" << param_name << "_local;\n"
+              << "}\n";
+          } else if (is_optional || maybe_none) {
             extra_convert << "}\n";
           }
 
@@ -5645,21 +5606,60 @@ write_function_instance(ostream &out, FunctionRemap *remap,
       } else { // The regular, non-coercion case.
         type->output_instance(extra_convert, param_name + "_this", &parser);
         if (is_optional && maybe_none) {
+          // This parameter has a default value of nullptr, so we need to also
+          // allow passing in None.
           extra_convert
             << default_expr << ";\n"
             << "if (" << param_name << " != nullptr && " << param_name << " != Py_None) {\n"
             << "  " << param_name << "_this";
-        } else if (is_optional) {
+        }
+        else if (is_optional && !TypeManager::is_pointer(orig_type) && !default_value->is_lvalue()) {
+          // Most annoying case, where we have to use an rvalue reference to
+          // extend the lifetime of the default argument.  In this case, the
+          // default expression is invoked even if not used.
+          extra_convert << ";\n";
+          if (TypeManager::is_const_pointer_to_anything(type)) {
+            extra_convert << "const ";
+            obj_type->output_instance(extra_convert, "&" + param_name + "_ref", &parser);
+          } else {
+            obj_type->output_instance(extra_convert, "&&" + param_name + "_ref", &parser);
+          }
           extra_convert
             << default_expr << ";\n"
-            << "if (" << param_name << " != nullptr) {\n"
+            << "if (" << param_name << " == nullptr) {\n"
+            << "  " << param_name << "_this = &" << param_name << "_ref;\n"
+            << "} else {\n"
             << "  " << param_name << "_this";
-        } else if (maybe_none) {
+        }
+        else if (is_optional) {
+          // General case where the default argument is either an lvalue or a
+          // pointer.
+          extra_convert
+            << ";\n"
+            << "if (" << param_name << " == nullptr) {\n"
+            << "  " << param_name << "_this = ";
+          if (TypeManager::is_pointer(orig_type)) {
+            default_value->output(extra_convert, 0, &parser, false);
+            extra_convert << ";\n";
+          } else {
+            // The rvalue case was handled above, so this is an lvalue, which
+            // means we can safely take a reference to it.
+            extra_convert << "&(";
+            default_value->output(extra_convert, 0, &parser, false);
+            extra_convert << ");\n";
+          }
+          extra_convert
+            << "} else {\n"
+            << "  " << param_name << "_this";
+        }
+        else if (maybe_none) {
+          // No default argument, but we still need to check for None.
           extra_convert
             << " = nullptr;\n"
             << "if (" << param_name << " != Py_None) {\n"
             << "  " << param_name << "_this";
         }
+
         if (const_ok && !report_errors) {
           // This function does the same thing in this case and is slightly
           // simpler.  But maybe we should just reorganize these functions

+ 4 - 1
dtool/src/interrogate/interrogate.cxx

@@ -312,7 +312,10 @@ main(int argc, char **argv) {
   string command_line;
   int i;
   for (i = 0; i < argc; i++) {
-    command_line += string(argv[i]) + " ";
+    if (i > 0) {
+      command_line += ' ';
+    }
+    command_line += string(argv[i]);
   }
 
   Filename fn;

+ 1 - 0
dtool/src/interrogate/interrogateBuilder.cxx

@@ -2024,6 +2024,7 @@ get_make_property(CPPMakeProperty *make_property, CPPStructType *struct_type, CP
     if (iproperty._type != 0 && iproperty._type != return_index) {
       cerr << "Property " << property_name << " has inconsistent element type!\n";
     }
+    iproperty._type = return_index;
   } else {
     iproperty._type = 0;
   }

+ 10 - 0
dtool/src/interrogatedb/dtool_super_base.cxx

@@ -15,6 +15,16 @@
 
 #ifdef HAVE_PYTHON
 
+static PyMemberDef standard_type_members[] = {
+  {(char *)"this", (sizeof(void*) == sizeof(int)) ? T_UINT : T_ULONGLONG, offsetof(Dtool_PyInstDef, _ptr_to_object), READONLY, (char *)"C++ 'this' pointer, if any"},
+  {(char *)"this_ownership", T_BOOL, offsetof(Dtool_PyInstDef, _memory_rules), READONLY, (char *)"C++ 'this' ownership rules"},
+  {(char *)"this_const", T_BOOL, offsetof(Dtool_PyInstDef, _is_const), READONLY, (char *)"C++ 'this' const flag"},
+// {(char *)"this_signature", T_INT, offsetof(Dtool_PyInstDef, _signature),
+// READONLY, (char *)"A type check signature"},
+  {(char *)"this_metatype", T_OBJECT, offsetof(Dtool_PyInstDef, _My_Type), READONLY, (char *)"The dtool meta object"},
+  {nullptr}  /* Sentinel */
+};
+
 static PyObject *GetSuperBase(PyObject *self) {
   Dtool_PyTypedObject *super_base = Dtool_GetSuperBase();
   Py_XINCREF((PyTypeObject *)super_base); // order is important .. this is used for static functions

+ 12 - 0
dtool/src/interrogatedb/interrogate_interface.cxx

@@ -176,6 +176,18 @@ interrogate_element_setter(ElementIndex element) {
   return InterrogateDatabase::get_ptr()->get_element(element).get_setter();
 }
 
+bool
+interrogate_element_is_sequence(ElementIndex element) {
+  // cerr << "interrogate_element_is_sequence(" << element << ")\n";
+  return InterrogateDatabase::get_ptr()->get_element(element).is_sequence();
+}
+
+bool
+interrogate_element_is_mapping(ElementIndex element) {
+  // cerr << "interrogate_element_is_mapping(" << element << ")\n";
+  return InterrogateDatabase::get_ptr()->get_element(element).is_mapping();
+}
+
 int
 interrogate_number_of_globals() {
   // cerr << "interrogate_number_of_globals()\n";

+ 3 - 0
dtool/src/interrogatedb/interrogate_interface.h

@@ -153,6 +153,9 @@ EXPCL_INTERROGATEDB FunctionIndex interrogate_element_getter(ElementIndex elemen
 EXPCL_INTERROGATEDB bool interrogate_element_has_setter(ElementIndex element);
 EXPCL_INTERROGATEDB FunctionIndex interrogate_element_setter(ElementIndex element);
 
+EXPCL_INTERROGATEDB bool interrogate_element_is_sequence(ElementIndex element);
+EXPCL_INTERROGATEDB bool interrogate_element_is_mapping(ElementIndex element);
+
 // Global Data
 
 // This is the list of global data elements.

+ 37 - 45
dtool/src/interrogatedb/py_panda.cxx

@@ -12,16 +12,6 @@
 
 using std::string;
 
-PyMemberDef standard_type_members[] = {
-  {(char *)"this", (sizeof(void*) == sizeof(int)) ? T_UINT : T_ULONGLONG, offsetof(Dtool_PyInstDef, _ptr_to_object), READONLY, (char *)"C++ 'this' pointer, if any"},
-  {(char *)"this_ownership", T_BOOL, offsetof(Dtool_PyInstDef, _memory_rules), READONLY, (char *)"C++ 'this' ownership rules"},
-  {(char *)"this_const", T_BOOL, offsetof(Dtool_PyInstDef, _is_const), READONLY, (char *)"C++ 'this' const flag"},
-// {(char *)"this_signature", T_INT, offsetof(Dtool_PyInstDef, _signature),
-// READONLY, (char *)"A type check signature"},
-  {(char *)"this_metatype", T_OBJECT, offsetof(Dtool_PyInstDef, _My_Type), READONLY, (char *)"The dtool meta object"},
-  {nullptr}  /* Sentinel */
-};
-
 /**
 
  */
@@ -588,46 +578,48 @@ PyObject *Dtool_PyModuleInitHelper(const LibraryDef *defs[], const char *modulen
         << "Python " << version << "\n";
     }
 
-    // Grab the __main__ module.
-    PyObject *main_module = PyImport_ImportModule("__main__");
-    if (main_module == nullptr) {
-      interrogatedb_cat.warning() << "Unable to import __main__\n";
-    }
-
-    // Extract the __file__ attribute, if present.
-    Filename main_dir;
-    PyObject *file_attr = nullptr;
-    if (main_module != nullptr) {
-      file_attr = PyObject_GetAttrString(main_module, "__file__");
-    }
-    if (file_attr == nullptr) {
-      // Must be running in the interactive interpreter.  Use the CWD.
-      main_dir = ExecutionEnvironment::get_cwd();
-    } else {
-#if PY_MAJOR_VERSION >= 3
-      Py_ssize_t length;
-      wchar_t *buffer = PyUnicode_AsWideCharString(file_attr, &length);
-      if (buffer != nullptr) {
-        main_dir = Filename::from_os_specific_w(std::wstring(buffer, length));
-        main_dir.make_absolute();
-        main_dir = main_dir.get_dirname();
-        PyMem_Free(buffer);
+    if (!ExecutionEnvironment::has_environment_variable("MAIN_DIR")) {
+      // Grab the __main__ module.
+      PyObject *main_module = PyImport_ImportModule("__main__");
+      if (main_module == NULL) {
+        interrogatedb_cat.warning() << "Unable to import __main__\n";
       }
-#else
-      char *buffer;
-      Py_ssize_t length;
-      if (PyString_AsStringAndSize(file_attr, &buffer, &length) != -1) {
-        main_dir = Filename::from_os_specific(std::string(buffer, length));
-        main_dir.make_absolute();
-        main_dir = main_dir.get_dirname();
+      
+      // Extract the __file__ attribute, if present.
+      Filename main_dir;
+      PyObject *file_attr = nullptr;
+      if (main_module != nullptr) {
+        file_attr = PyObject_GetAttrString(main_module, "__file__");
       }
+      if (file_attr == nullptr) {
+        // Must be running in the interactive interpreter.  Use the CWD.
+        main_dir = ExecutionEnvironment::get_cwd();
+      } else {
+#if PY_MAJOR_VERSION >= 3
+        Py_ssize_t length;
+        wchar_t *buffer = PyUnicode_AsWideCharString(file_attr, &length);
+        if (buffer != nullptr) {
+          main_dir = Filename::from_os_specific_w(std::wstring(buffer, length));
+          main_dir.make_absolute();
+          main_dir = main_dir.get_dirname();
+          PyMem_Free(buffer);
+        }
+#else
+        char *buffer;
+        Py_ssize_t length;
+        if (PyString_AsStringAndSize(file_attr, &buffer, &length) != -1) {
+          main_dir = Filename::from_os_specific(std::string(buffer, length));
+          main_dir.make_absolute();
+          main_dir = main_dir.get_dirname();
+        }
 #endif
-      else {
-        interrogatedb_cat.warning() << "Invalid string for __main__.__file__\n";
+        else {
+          interrogatedb_cat.warning() << "Invalid string for __main__.__file__\n";
+        }
       }
+      ExecutionEnvironment::shadow_environment_variable("MAIN_DIR", main_dir.to_os_specific());
+      PyErr_Clear();
     }
-    ExecutionEnvironment::shadow_environment_variable("MAIN_DIR", main_dir.to_os_specific());
-    PyErr_Clear();
     initialized_main_dir = true;
 
     // Also, while we are at it, initialize the thread swap hook.

+ 0 - 3
dtool/src/interrogatedb/py_panda.h

@@ -67,9 +67,6 @@ struct Dtool_PyInstDef {
   bool _is_const;
 };
 
-// A Offset Dictionary Defining How to read the Above Object..
-extern PyMemberDef standard_type_members[];
-
 // The Class Definition Structor For a Dtool python type.
 struct Dtool_PyTypedObject {
   // Standard Python Features..

+ 5 - 0
dtool/src/parser-inc/XInput.h

@@ -0,0 +1,5 @@
+typedef struct _XINPUT_STATE XINPUT_STATE, *PXINPUT_STATE;
+typedef struct _XINPUT_CAPABILITIES XINPUT_CAPABILITIES, *PXINPUT_CAPABILITIES;
+typedef struct _XINPUT_VIBRATION XINPUT_VIBRATION, *PXINPUT_VIBRATION;
+typedef struct _XINPUT_GAMEPAD XINPUT_GAMEPAD, *PXINPUT_GAMEPAD;
+typedef struct _XINPUT_KEYSTROKE XINPUT_KEYSTROKE, *PXINPUT_KEYSTROKE;

+ 2 - 0
dtool/src/parser-inc/windows.h

@@ -38,8 +38,10 @@ typedef unsigned long ULONG_PTR;
 // http://msdn.microsoft.com/en-us/library/cc230309.aspx
 typedef bool BOOL;
 typedef unsigned long DWORD;
+typedef unsigned short WORD;
 typedef long LONG;
 typedef long UINT;
+typedef unsigned char BYTE;
 typedef unsigned long ULONG;
 typedef long long LONGLONG;
 typedef long HRESULT;

+ 90 - 3
dtool/src/prc/configPageManager.cxx

@@ -38,6 +38,10 @@
 #include <algorithm>
 #include <ctype.h>
 
+#ifndef _MSC_VER
+#include <dlfcn.h>
+#endif
+
 using std::string;
 
 ConfigPageManager *ConfigPageManager::_global_ptr = nullptr;
@@ -93,11 +97,60 @@ reload_implicit_pages() {
   }
   _implicit_pages.clear();
 
+  // If we are running inside a deployed application, see if it exposes
+  // information about how the PRC data should be initialized.
+  struct BlobInfo {
+    uint64_t blob_offset;
+    uint64_t blob_size;
+    uint16_t version;
+    uint16_t num_pointers;
+    uint16_t codepage;
+    uint16_t flags;
+    uint64_t reserved;
+    const void *module_table;
+    const char *prc_data;
+    const char *default_prc_dir;
+    const char *prc_dir_envvars;
+    const char *prc_path_envvars;
+    const char *prc_patterns;
+    const char *prc_encrypted_patterns;
+    const char *prc_encryption_key;
+    const char *prc_executable_patterns;
+    const char *prc_executable_args_envvar;
+    const char *main_dir;
+    const char *log_filename;
+  };
+#ifdef _MSC_VER
+  const BlobInfo *blobinfo = (const BlobInfo *)GetProcAddress(GetModuleHandle(NULL), "blobinfo");
+#elif defined(RTLD_MAIN_ONLY)
+  const BlobInfo *blobinfo = (const BlobInfo *)dlsym(RTLD_MAIN_ONLY, "blobinfo");
+//#elif defined(RTLD_SELF)
+//  const BlobInfo *blobinfo = (const BlobInfo *)dlsym(RTLD_SELF, "blobinfo");
+#else
+  const BlobInfo *blobinfo = (const BlobInfo *)dlsym(dlopen(NULL, RTLD_NOW), "blobinfo");
+#endif
+  if (blobinfo != nullptr && (blobinfo->version == 0 || blobinfo->num_pointers < 10)) {
+    blobinfo = nullptr;
+  }
+
+  if (blobinfo != nullptr) {
+    if (blobinfo->num_pointers >= 11 && blobinfo->main_dir != nullptr) {
+      ExecutionEnvironment::set_environment_variable("MAIN_DIR", blobinfo->main_dir);
+    } else {
+      // Make sure that py_panda.cxx won't override MAIN_DIR.
+      ExecutionEnvironment::set_environment_variable("MAIN_DIR",
+        ExecutionEnvironment::get_environment_variable("MAIN_DIR"));
+    }
+  }
+
   // PRC_PATTERNS lists one or more filename templates separated by spaces.
   // Pull them out and store them in _prc_patterns.
   _prc_patterns.clear();
 
   string prc_patterns = PRC_PATTERNS;
+  if (blobinfo != nullptr && blobinfo->prc_patterns != nullptr) {
+    prc_patterns = blobinfo->prc_patterns;
+  }
   if (!prc_patterns.empty()) {
     vector_string pat_list;
     ConfigDeclaration::extract_words(prc_patterns, pat_list);
@@ -117,6 +170,9 @@ reload_implicit_pages() {
   _prc_encrypted_patterns.clear();
 
   string prc_encrypted_patterns = PRC_ENCRYPTED_PATTERNS;
+  if (blobinfo != nullptr && blobinfo->prc_encrypted_patterns != nullptr) {
+    prc_encrypted_patterns = blobinfo->prc_encrypted_patterns;
+  }
   if (!prc_encrypted_patterns.empty()) {
     vector_string pat_list;
     ConfigDeclaration::extract_words(prc_encrypted_patterns, pat_list);
@@ -134,6 +190,9 @@ reload_implicit_pages() {
   _prc_executable_patterns.clear();
 
   string prc_executable_patterns = PRC_EXECUTABLE_PATTERNS;
+  if (blobinfo != nullptr && blobinfo->prc_executable_patterns != nullptr) {
+    prc_executable_patterns = blobinfo->prc_executable_patterns;
+  }
   if (!prc_executable_patterns.empty()) {
     vector_string pat_list;
     ConfigDeclaration::extract_words(prc_executable_patterns, pat_list);
@@ -154,6 +213,9 @@ reload_implicit_pages() {
   // spaces.  Pull them out, and each of those contains the name of a single
   // directory to search.  Add it to the search path.
   string prc_dir_envvars = PRC_DIR_ENVVARS;
+  if (blobinfo != nullptr && blobinfo->prc_dir_envvars != nullptr) {
+    prc_dir_envvars = blobinfo->prc_dir_envvars;
+  }
   if (!prc_dir_envvars.empty()) {
     vector_string prc_dir_envvar_list;
     ConfigDeclaration::extract_words(prc_dir_envvars, prc_dir_envvar_list);
@@ -173,6 +235,9 @@ reload_implicit_pages() {
   // spaces.  Pull them out, and then each one of those contains a list of
   // directories to search.  Add each of those to the search path.
   string prc_path_envvars = PRC_PATH_ENVVARS;
+  if (blobinfo != nullptr && blobinfo->prc_path_envvars != nullptr) {
+    prc_path_envvars = blobinfo->prc_path_envvars;
+  }
   if (!prc_path_envvars.empty()) {
     vector_string prc_path_envvar_list;
     ConfigDeclaration::extract_words(prc_path_envvars, prc_path_envvar_list);
@@ -204,7 +269,7 @@ reload_implicit_pages() {
  * DEFAULT_PATHSEP.
  */
   string prc_path2_envvars = PRC_PATH2_ENVVARS;
-  if (!prc_path2_envvars.empty()) {
+  if (!prc_path2_envvars.empty() && blobinfo == nullptr) {
     vector_string prc_path_envvar_list;
     ConfigDeclaration::extract_words(prc_path2_envvars, prc_path_envvar_list);
     for (size_t i = 0; i < prc_path_envvar_list.size(); ++i) {
@@ -228,6 +293,9 @@ reload_implicit_pages() {
     // If nothing's on the search path (PRC_DIR and PRC_PATH were not
     // defined), then use the DEFAULT_PRC_DIR.
     string default_prc_dir = DEFAULT_PRC_DIR;
+    if (blobinfo != nullptr && blobinfo->default_prc_dir != nullptr) {
+      default_prc_dir = blobinfo->default_prc_dir;
+    }
     if (!default_prc_dir.empty()) {
       // It's already from-os-specific by ppremake.
       Filename prc_dir_filename = default_prc_dir;
@@ -300,12 +368,24 @@ reload_implicit_pages() {
     }
   }
 
+  int i = 1;
+
+  // If prc_data is predefined, we load it as an implicit page.
+  if (blobinfo != nullptr && blobinfo->prc_data != nullptr) {
+    ConfigPage *page = new ConfigPage("builtin", true, i);
+    ++i;
+    _implicit_pages.push_back(page);
+    _pages_sorted = false;
+
+    std::istringstream in(blobinfo->prc_data);
+    page->read_prc(in);
+  }
+
   // Now we have a list of filenames in order from most important to least
   // important.  Walk through the list in reverse order to load their
   // contents, because we want the first file in the list (the most important)
   // to be on the top of the stack.
   ConfigFiles::reverse_iterator ci;
-  int i = 1;
   for (ci = config_files.rbegin(); ci != config_files.rend(); ++ci) {
     const ConfigFile &file = (*ci);
     Filename filename = file._filename;
@@ -316,6 +396,9 @@ reload_implicit_pages() {
       string command = filename.to_os_specific();
 
       string envvar = PRC_EXECUTABLE_ARGS_ENVVAR;
+      if (blobinfo != nullptr && blobinfo->prc_executable_args_envvar != nullptr) {
+        envvar = blobinfo->prc_executable_args_envvar;
+      }
       if (!envvar.empty()) {
         string args = ExecutionEnvironment::get_environment_variable(envvar);
         if (!args.empty()) {
@@ -346,7 +429,11 @@ reload_implicit_pages() {
         _implicit_pages.push_back(page);
         _pages_sorted = false;
 
-        page->read_encrypted_prc(in, PRC_ENCRYPTION_KEY);
+        if (blobinfo != nullptr && blobinfo->prc_encryption_key != nullptr) {
+          page->read_encrypted_prc(in, blobinfo->prc_encryption_key);
+        } else {
+          page->read_encrypted_prc(in, PRC_ENCRYPTION_KEY);
+        }
       }
 
     } else if ((file._file_flags & FF_read) != 0) {

+ 275 - 174
makepanda/installer.nsi

@@ -14,7 +14,7 @@
 ;
 ;   BUILT         - location of panda install tree.
 ;   SOURCE        - location of the panda source-tree if available, OR location of panda install tree.
-;   PYVER         - version of Python that Panda was built with (ie, "2.7")
+;   INCLUDE_PYVER - version of Python that Panda was built with (eg. "2.7", "3.5-32")
 ;   REGVIEW       - either 32 or 64, depending on the build architecture.
 ;
 
@@ -46,7 +46,9 @@ SetCompressor ${COMPRESSOR}
 !insertmacro MUI_PAGE_LICENSE "${SOURCE}/doc/LICENSE"
 !insertmacro MUI_PAGE_DIRECTORY
 
+!ifdef INCLUDE_PYVER
 !define MUI_PAGE_CUSTOMFUNCTION_LEAVE ConfirmPythonSelection
+!endif
 !insertmacro MUI_PAGE_COMPONENTS
 
 !insertmacro MUI_PAGE_INSTFILES
@@ -64,8 +66,9 @@ ShowUninstDetails hide
 
 LicenseData "${SOURCE}/doc/LICENSE"
 
-InstType "Full (Recommended)"
-InstType "Minimal"
+InstType "Auto (Recommended)"
+InstType "Full"
+InstType "Light"
 
 LangString DESC_SecCore ${LANG_ENGLISH} "The Panda3D core libraries, configuration files and models/textures that are needed to use Panda3D."
 LangString DESC_SecOpenGL ${LANG_ENGLISH} "The OpenGL graphics back-end is the most well-supported renderer."
@@ -78,8 +81,9 @@ LangString DESC_SecODE ${LANG_ENGLISH} "Support for the Open Dynamics Engine to
 LangString DESC_SecPhysX ${LANG_ENGLISH} "Support for NVIDIA PhysX to implement physics."
 LangString DESC_SecRocket ${LANG_ENGLISH} "Support for the libRocket GUI library.  This is an optional library that offers an HTML/CSS-like approach to creating user interfaces."
 LangString DESC_SecTools ${LANG_ENGLISH} "Useful tools and model converters to help with Panda3D development.  Recommended."
-LangString DESC_SecPyBindings ${LANG_ENGLISH} "Contains the Python modules that allow use of Panda3D using Python.  These will only work with a ${REGVIEW}-bit version of Python ${PYVER}."
-LangString DESC_SecPython ${LANG_ENGLISH} "Contains a ${REGVIEW}-bit copy of Python ${PYVER} preconfigured to make use of Panda3D."
+LangString DESC_SecGroupPython ${LANG_ENGLISH} "Contains modules that provide Python support for Panda3D."
+LangString DESC_SecPyShared ${LANG_ENGLISH} "Contains the common Python code used by the Panda3D Python bindings."
+LangString DESC_SecPython ${LANG_ENGLISH} "Contains a ${REGVIEW}-bit copy of Python ${INCLUDE_PYVER} preconfigured to make use of Panda3D."
 LangString DESC_SecHeadersLibs ${LANG_ENGLISH} "Headers and libraries needed for C++ development with Panda3D."
 LangString DESC_SecSamples ${LANG_ENGLISH} "The sample programs demonstrate how to make Python applications with Panda3D."
 LangString DESC_SecMaxPlugins ${LANG_ENGLISH} "Plug-ins for Autodesk 3ds Max (${REGVIEW}-bit) that can be used to export models to Panda3D."
@@ -112,26 +116,109 @@ var MANPAGE
 !insertmacro !defineifexist HAVE_ODE "${BUILT}\bin\libpandaode.dll"
 !insertmacro !defineifexist HAVE_PHYSX "${BUILT}\bin\libpandaphysx.dll"
 !insertmacro !defineifexist HAVE_ROCKET "${BUILT}\bin\libp3rocket.dll"
-!insertmacro !defineifexist HAVE_PYTHON "${BUILT}\python"
 !insertmacro !defineifexist HAVE_SAMPLES "${SOURCE}\samples"
 !insertmacro !defineifexist HAVE_MAX_PLUGINS "${BUILT}\plugins\*.dlo"
 !insertmacro !defineifexist HAVE_MAYA_PLUGINS "${BUILT}\plugins\*.mll"
 
+!macro RemovePythonPath PYVER
+    ReadRegStr $0 HKCU "Software\Python\PythonCore\${PYVER}\PythonPath\Panda3D" ""
+    StrCmp $0 "$INSTDIR" 0 +2
+    DeleteRegKey HKCU "Software\Python\PythonCore\${PYVER}\PythonPath\Panda3D"
+!macroend
+
+!macro PyBindingSection PYVER EXT_SUFFIX
+    LangString DESC_SecPyBindings${PYVER} ${LANG_ENGLISH} "Contains the Python modules that allow use of Panda3D using a ${REGVIEW}-bit version of Python ${PYVER}."
+
+    !insertmacro !defineifexist _present "${BUILT}\panda3d\core${EXT_SUFFIX}"
+    !ifdef _present
+    Section "${PYVER} bindings" SecPyBindings${PYVER}
+        !if "${PYVER}" == "${INCLUDE_PYVER}"
+            SectionIn 1 2 3
+        !else
+            !if "${PYVER}" == "2.7"
+                SectionIn 1 2
+            !else
+                ; See .onInit function where this is dynamically enabled.
+                SectionIn 2
+            !endif
+        !endif
+
+        SetDetailsPrint both
+        DetailPrint "Installing Panda3D bindings for Python ${PYVER}..."
+        SetDetailsPrint listonly
+
+        SetOutPath $INSTDIR\panda3d
+
+        File /nonfatal /r "${BUILT}\panda3d\core${EXT_SUFFIX}"
+        File /nonfatal /r "${BUILT}\panda3d\ai${EXT_SUFFIX}"
+        File /nonfatal /r "${BUILT}\panda3d\direct${EXT_SUFFIX}"
+        File /nonfatal /r "${BUILT}\panda3d\egg${EXT_SUFFIX}"
+        File /nonfatal /r "${BUILT}\panda3d\fx${EXT_SUFFIX}"
+        File /nonfatal /r "${BUILT}\panda3d\interrogatedb${EXT_SUFFIX}"
+        File /nonfatal /r "${BUILT}\panda3d\physics${EXT_SUFFIX}"
+        File /nonfatal /r "${BUILT}\panda3d\_rplight${EXT_SUFFIX}"
+        File /nonfatal /r "${BUILT}\panda3d\skel${EXT_SUFFIX}"
+        File /nonfatal /r "${BUILT}\panda3d\vision${EXT_SUFFIX}"
+        File /nonfatal /r "${BUILT}\panda3d\vrpn${EXT_SUFFIX}"
+
+        !ifdef HAVE_BULLET
+            SectionGetFlags ${SecBullet} $R0
+            IntOp $R0 $R0 & ${SF_SELECTED}
+            StrCmp $R0 ${SF_SELECTED} 0 SkipBulletPyd
+            File /nonfatal /r "${BUILT}\panda3d\bullet${EXT_SUFFIX}"
+            SkipBulletPyd:
+        !endif
+
+        !ifdef HAVE_ODE
+            SectionGetFlags ${SecODE} $R0
+            IntOp $R0 $R0 & ${SF_SELECTED}
+            StrCmp $R0 ${SF_SELECTED} 0 SkipODEPyd
+            File /nonfatal /r "${BUILT}\panda3d\ode${EXT_SUFFIX}"
+            SkipODEPyd:
+        !endif
+
+        !ifdef HAVE_PHYSX
+            SectionGetFlags ${SecPhysX} $R0
+            IntOp $R0 $R0 & ${SF_SELECTED}
+            StrCmp $R0 ${SF_SELECTED} 0 SkipPhysXPyd
+            File /nonfatal /r "${BUILT}\panda3d\physx${EXT_SUFFIX}"
+            SkipPhysXPyd:
+        !endif
+
+        !ifdef HAVE_ROCKET
+            SectionGetFlags ${SecRocket} $R0
+            IntOp $R0 $R0 & ${SF_SELECTED}
+            StrCmp $R0 ${SF_SELECTED} 0 SkipRocketPyd
+            File /nonfatal /r "${BUILT}\panda3d\rocket${EXT_SUFFIX}"
+            SkipRocketPyd:
+        !endif
+
+        SetOutPath $INSTDIR\pandac\input
+        File /r "${BUILT}\pandac\input\*"
+        SetOutPath $INSTDIR\Pmw
+        File /nonfatal /r /x CVS "${BUILT}\Pmw\*"
+        SetOutPath $INSTDIR\panda3d.dist-info
+        File /nonfatal /r "${BUILT}\panda3d.dist-info\*"
+
+        !ifdef REGVIEW
+        SetRegView ${REGVIEW}
+        !endif
+
+        ; Install a Panda3D path into the global PythonPath for this version
+        ; of Python.
+        WriteRegStr HKCU "Software\Python\PythonCore\${PYVER}\PythonPath\Panda3D" "" "$INSTDIR"
+    SectionEnd
+    !undef _present
+    !endif
+!macroend
+
 Function runFunction
     ExecShell "open" "$SMPROGRAMS\${TITLE}\Panda3D Manual.lnk"
 FunctionEnd
 
-Function .onInit
-    ${If} ${REGVIEW} = 64
-    ${AndIfNot} ${RunningX64}
-        MessageBox MB_OK|MB_ICONEXCLAMATION "You are attempting to install the 64-bit version of Panda3D on a 32-bit version of Windows.  Please download and install the 32-bit version of Panda3D instead."
-        Abort
-    ${EndIf}
-FunctionEnd
-
 SectionGroup "Panda3D Libraries"
     Section "Core Libraries" SecCore
-        SectionIn 1 2 RO
+        SectionIn 1 2 3 RO
 
         SetShellVarContext current
         SetOverwrite try
@@ -177,7 +264,7 @@ SectionGroup "Panda3D Libraries"
 
     !ifdef HAVE_GL
     Section "OpenGL" SecOpenGL
-        SectionIn 1 2 RO
+        SectionIn 1 2 3 RO
 
         SetOutPath "$INSTDIR\bin"
         File "${BUILT}\bin\libpandagl.dll"
@@ -186,7 +273,7 @@ SectionGroup "Panda3D Libraries"
 
     !ifdef HAVE_DX9
     Section "Direct3D 9" SecDirect3D9
-        SectionIn 1
+        SectionIn 1 2
 
         SetOutPath "$INSTDIR\bin"
         File "${BUILT}\bin\libpandadx9.dll"
@@ -196,7 +283,7 @@ SectionGroup "Panda3D Libraries"
 
     !ifdef HAVE_OPENAL
     Section "OpenAL Audio" SecOpenAL
-        SectionIn 1 2
+        SectionIn 1 2 3
 
         SetOutPath "$INSTDIR\bin"
         File "${BUILT}\bin\libp3openal_audio.dll"
@@ -207,7 +294,7 @@ SectionGroup "Panda3D Libraries"
 
     !ifdef HAVE_FMOD
     Section "FMOD Audio" SecFMOD
-        SectionIn 1
+        SectionIn 1 2
 
         SetOutPath "$INSTDIR\bin"
         File "${BUILT}\bin\libp3fmod_audio.dll"
@@ -217,7 +304,7 @@ SectionGroup "Panda3D Libraries"
 
     !ifdef HAVE_FFMPEG
     Section "FFMpeg" SecFFMpeg
-        SectionIn 1
+        SectionIn 1 2
 
         SetOutPath "$INSTDIR\bin"
         File "${BUILT}\bin\libp3ffmpeg.dll"
@@ -230,7 +317,7 @@ SectionGroup "Panda3D Libraries"
 
     !ifdef HAVE_BULLET
     Section "Bullet Physics" SecBullet
-        SectionIn 1
+        SectionIn 1 2
 
         SetOutPath "$INSTDIR\bin"
         File "${BUILT}\bin\libpandabullet.dll"
@@ -239,7 +326,7 @@ SectionGroup "Panda3D Libraries"
 
     !ifdef HAVE_ODE
     Section "ODE Physics" SecODE
-        SectionIn 1
+        SectionIn 1 2
 
         SetOutPath "$INSTDIR\bin"
         File "${BUILT}\bin\libpandaode.dll"
@@ -248,7 +335,8 @@ SectionGroup "Panda3D Libraries"
 
     !ifdef HAVE_PHYSX
     Section "NVIDIA PhysX" SecPhysX
-        SectionIn 1
+        ; Only enable in "Full"
+        SectionIn 2
 
         SetOutPath "$INSTDIR\bin"
         File "${BUILT}\bin\libpandaphysx.dll"
@@ -260,7 +348,7 @@ SectionGroup "Panda3D Libraries"
 
     !ifdef HAVE_ROCKET
     Section "libRocket GUI" SecRocket
-        SectionIn 1
+        SectionIn 1 2
 
         SetOutPath "$INSTDIR\bin"
         File "${BUILT}\bin\libp3rocket.dll"
@@ -272,14 +360,14 @@ SectionGroup "Panda3D Libraries"
 SectionGroupEnd
 
 Section "Tools and utilities" SecTools
-    SectionIn 1 2
+    SectionIn 1 2 3
 
     SetDetailsPrint both
     DetailPrint "Installing utilities..."
     SetDetailsPrint listonly
 
     SetOutPath "$INSTDIR\bin"
-    File /r "${BUILT}\bin\*.exe"
+    File /r /x deploy-stub.exe /x deploy-stubw.exe "${BUILT}\bin\*.exe"
     File /nonfatal /r "${BUILT}\bin\*.p3d"
     SetOutPath "$INSTDIR\NSIS"
     File /r /x CVS "${NSISDIR}\*"
@@ -305,21 +393,16 @@ Section "Tools and utilities" SecTools
     WriteRegStr HKCU "Software\Classes\Panda3D.Multifile\shell\extract\command" "" '"$INSTDIR\bin\multify.exe" -xf "%1"'
 SectionEnd
 
-SectionGroup "Python support"
-    Section "Python bindings" SecPyBindings
-        SectionIn 1 2
+SectionGroup "Python modules" SecGroupPython
+    Section "Shared code" SecPyShared
+        SectionIn 1 2 3
 
         SetDetailsPrint both
-        DetailPrint "Installing Panda3D Python modules..."
+        DetailPrint "Installing Panda3D shared Python modules..."
         SetDetailsPrint listonly
 
-        SetOutPath "$INSTDIR\bin"
-        File /nonfatal /r "${BUILT}\bin\*.pyd"
-
         SetOutPath $INSTDIR\direct\directscripts
         File /r /x CVS /x Opt?-Win32 "${BUILT}\direct\directscripts\*"
-        SetOutPath $INSTDIR\direct\filter
-        File /r /x CVS /x Opt?-Win32 "${BUILT}\direct\filter\*.sha"
         SetOutPath $INSTDIR\direct
         File /r /x CVS /x Opt?-Win32 "${BUILT}\direct\*.py"
 
@@ -330,142 +413,133 @@ SectionGroup "Python support"
         File /r "${BUILT}\pandac\*.py"
         SetOutPath $INSTDIR\panda3d
         File /r "${BUILT}\panda3d\*.py"
+    SectionEnd
 
-        File /nonfatal /r "${BUILT}\panda3d\core${EXT_SUFFIX}"
-        File /nonfatal /r "${BUILT}\panda3d\ai${EXT_SUFFIX}"
-        File /nonfatal /r "${BUILT}\panda3d\awesomium${EXT_SUFFIX}"
-        File /nonfatal /r "${BUILT}\panda3d\direct${EXT_SUFFIX}"
-        File /nonfatal /r "${BUILT}\panda3d\egg${EXT_SUFFIX}"
-        File /nonfatal /r "${BUILT}\panda3d\fx${EXT_SUFFIX}"
-        File /nonfatal /r "${BUILT}\panda3d\interrogatedb${EXT_SUFFIX}"
-        File /nonfatal /r "${BUILT}\panda3d\physics${EXT_SUFFIX}"
-        File /nonfatal /r "${BUILT}\panda3d\_rplight${EXT_SUFFIX}"
-        File /nonfatal /r "${BUILT}\panda3d\skel${EXT_SUFFIX}"
-        File /nonfatal /r "${BUILT}\panda3d\vision${EXT_SUFFIX}"
-        File /nonfatal /r "${BUILT}\panda3d\vrpn${EXT_SUFFIX}"
-
-        !ifdef HAVE_BULLET
-            SectionGetFlags ${SecBullet} $R0
-            IntOp $R0 $R0 & ${SF_SELECTED}
-            StrCmp $R0 ${SF_SELECTED} 0 SkipBulletPyd
-            File /nonfatal /r "${BUILT}\panda3d\bullet${EXT_SUFFIX}"
-            SkipBulletPyd:
-        !endif
-
-        !ifdef HAVE_ODE
-            SectionGetFlags ${SecODE} $R0
-            IntOp $R0 $R0 & ${SF_SELECTED}
-            StrCmp $R0 ${SF_SELECTED} 0 SkipODEPyd
-            File /nonfatal /r "${BUILT}\panda3d\ode${EXT_SUFFIX}"
-            SkipODEPyd:
-        !endif
-
-        !ifdef HAVE_PHYSX
-            SectionGetFlags ${SecPhysX} $R0
-            IntOp $R0 $R0 & ${SF_SELECTED}
-            StrCmp $R0 ${SF_SELECTED} 0 SkipPhysXPyd
-            File /nonfatal /r "${BUILT}\panda3d\physx${EXT_SUFFIX}"
-            SkipPhysXPyd:
-        !endif
-
-        !ifdef HAVE_ROCKET
-            SectionGetFlags ${SecRocket} $R0
-            IntOp $R0 $R0 & ${SF_SELECTED}
-            StrCmp $R0 ${SF_SELECTED} 0 SkipRocketPyd
-            File /nonfatal /r "${BUILT}\panda3d\rocket${EXT_SUFFIX}"
-            SkipRocketPyd:
-        !endif
+    !insertmacro PyBindingSection 2.7 .pyd
+    !if "${REGVIEW}" == "32"
+        !insertmacro PyBindingSection 3.5-32 .cp35-win32.pyd
+        !insertmacro PyBindingSection 3.6-32 .cp36-win32.pyd
+        !insertmacro PyBindingSection 3.7-32 .cp37-win32.pyd
+        !insertmacro PyBindingSection 3.8-32 .cp38-win32.pyd
+        !insertmacro PyBindingSection 3.9-32 .cp39-win32.pyd
+    !else
+        !insertmacro PyBindingSection 3.5 .cp35-win_amd64.pyd
+        !insertmacro PyBindingSection 3.6 .cp36-win_amd64.pyd
+        !insertmacro PyBindingSection 3.7 .cp37-win_amd64.pyd
+        !insertmacro PyBindingSection 3.8 .cp38-win_amd64.pyd
+        !insertmacro PyBindingSection 3.9 .cp39-win_amd64.pyd
+    !endif
+SectionGroupEnd
 
-        SetOutPath $INSTDIR\pandac\input
-        File /r "${BUILT}\pandac\input\*"
-        SetOutPath $INSTDIR\Pmw
-        File /nonfatal /r /x CVS "${BUILT}\Pmw\*"
+!ifdef INCLUDE_PYVER
+Section "Python ${INCLUDE_PYVER}" SecPython
+    SectionIn 1 2 3
 
-        !ifdef REGVIEW
-        SetRegView ${REGVIEW}
-        !endif
+    !ifdef REGVIEW
+    SetRegView ${REGVIEW}
+    !endif
 
-        ; Check for a non-Panda3D system-wide Python installation.
-        ReadRegStr $0 HKLM "Software\Python\PythonCore\${PYVER}\InstallPath" ""
-        StrCmp $0 "$INSTDIR\python" UserExternalPthCheck 0
-        StrCmp $0 "" UserExternalPthCheck 0
-        IfFileExists "$0\ppython.exe" UserExternalPthCheck 0
-        IfFileExists "$0\python.exe" AskExternalPth UserExternalPthCheck
-
-        ; Check for a non-Panda3D user installation of Python.
-        UserExternalPthCheck:
-        ReadRegStr $0 HKCU "Software\Python\PythonCore\${PYVER}\InstallPath" ""
-        StrCmp $0 "$INSTDIR\python" SkipExternalPth 0
-        StrCmp $0 "" SkipExternalPth 0
-        IfFileExists "$0\ppython.exe" SkipExternalPth 0
-        IfFileExists "$0\python.exe" AskExternalPth SkipExternalPth
-
-        ; We're pretty sure this Python build is of the right architecture.
-        AskExternalPth:
-        MessageBox MB_YESNO|MB_ICONQUESTION \
-            "Your system already has a copy of Python ${PYVER} installed in:$\r$\n$0$\r$\nWould you like to configure it to be able to use the Panda3D libraries?$\r$\nIf you choose no, you will only be able to use Panda3D's own copy of Python." \
-            IDYES WriteExternalPth IDNO SkipExternalPth
-
-        WriteExternalPth:
-        ;FileOpen $1 "$0\Lib\site-packages\panda.pth" w
-        ;FileWrite $1 "$INSTDIR$\r$\n"
-        ;FileWrite $1 "$INSTDIR\bin$\r$\n"
-        ;FileClose $1
-
-        ; Actually, it looks like we can just do this instead:
-        WriteRegStr HKCU "Software\Python\PythonCore\${PYVER}\PythonPath\Panda3D" "" "$INSTDIR"
+    SetDetailsPrint both
+    DetailPrint "Installing Python ${INCLUDE_PYVER} interpreter (${REGVIEW}-bit)..."
+    SetDetailsPrint listonly
 
-        SkipExternalPth:
-    SectionEnd
+    SetOutPath "$INSTDIR\bin"
+    File /nonfatal "${BUILT}\bin\python*.dll"
 
-    !ifdef HAVE_PYTHON
-    Section "Python ${PYVER}" SecPython
-        SectionIn 1 2
+    SetOutPath "$INSTDIR\python"
+    File /r /x *.pdb "${BUILT}\python\*"
 
-        !ifdef REGVIEW
-        SetRegView ${REGVIEW}
-        !endif
+    SetDetailsPrint both
+    DetailPrint "Adding registry keys for Python..."
+    SetDetailsPrint listonly
 
-        SetDetailsPrint both
-        DetailPrint "Installing Python ${PYVER} (${REGVIEW}-bit)..."
-        SetDetailsPrint listonly
+    ; Check if a copy of Python is installed for this user.
+    ReadRegStr $0 HKCU "Software\Python\PythonCore\${INCLUDE_PYVER}\InstallPath" ""
+    StrCmp "$0" "$INSTDIR\python" RegPath 0
+    StrCmp "$0" "" SkipFileCheck 0
+    IfFileExists "$0\python.exe" AskRegPath 0
+    SkipFileCheck:
 
-        SetOutPath "$INSTDIR\bin"
-        File /nonfatal "${BUILT}\bin\python*.dll"
+    ; Check if a system-wide copy of Python is installed.
+    ReadRegStr $0 HKLM "Software\Python\PythonCore\${INCLUDE_PYVER}\InstallPath" ""
+    StrCmp "$0" "$INSTDIR\python" RegPath 0
+    StrCmp "$0" "" RegPath 0
+    IfFileExists "$0\python.exe" AskRegPath RegPath
 
-        SetOutPath "$INSTDIR\python"
-        File /r /x *.pdb "${BUILT}\python\*"
+    AskRegPath:
+    MessageBox MB_YESNO|MB_ICONQUESTION \
+        "You already have a copy of Python ${INCLUDE_PYVER} installed in:$\r$\n$0$\r$\n$\r$\nPanda3D installs its own copy of Python ${INCLUDE_PYVER}, which will install alongside your existing copy.  Would you like to make Panda's copy the default Python for your user account?" \
+        IDNO SkipRegPath
 
-        SetDetailsPrint both
-        DetailPrint "Adding registry keys for Python..."
-        SetDetailsPrint listonly
+    RegPath:
+    WriteRegStr HKCU "Software\Python\PythonCore\${INCLUDE_PYVER}\InstallPath" "" "$INSTDIR\python"
+    WriteRegStr HKCU "Software\Python\PythonCore\${INCLUDE_PYVER}\InstallPath" "ExecutablePath" "$INSTDIR\python\python.exe"
+    SkipRegPath:
 
-        ; Check if a copy of Python is installed for this user.
-        ReadRegStr $0 HKCU "Software\Python\PythonCore\${PYVER}\InstallPath" ""
-        StrCmp "$0" "$INSTDIR\python" RegPath 0
-        StrCmp "$0" "" SkipFileCheck 0
-        IfFileExists "$0\python.exe" AskRegPath 0
-        SkipFileCheck:
+SectionEnd
+!endif
 
-        ; Check if a system-wide copy of Python is installed.
-        ReadRegStr $0 HKLM "Software\Python\PythonCore\${PYVER}\InstallPath" ""
-        StrCmp "$0" "$INSTDIR\python" RegPath 0
-        StrCmp "$0" "" RegPath 0
-        IfFileExists "$0\python.exe" AskRegPath RegPath
+!macro MaybeEnablePyBindingSection PYVER
+    !if "${INCLUDE_PYVER}" != "${PYVER}"
+        !ifdef SecPyBindings${PYVER}
+            ; Check if a copy of Python is installed for this user.
+            Push $0
+            ReadRegStr $0 HKCU "Software\Python\PythonCore\${PYVER}\InstallPath" ""
+            StrCmp "$0" "" +2 0
+            IfFileExists "$0\python.exe" Py${PYVER}Exists 0
+
+            ; Check if a system-wide copy of Python is installed.
+            ReadRegStr $0 HKLM "Software\Python\PythonCore\${PYVER}\InstallPath" ""
+            StrCmp "$0" "" Py${PYVER}ExistsNot 0
+            IfFileExists "$0\python.exe" Py${PYVER}Exists Py${PYVER}ExistsNot
+
+            Py${PYVER}Exists:
+            SectionSetFlags ${SecPyBindings${PYVER}} ${SF_SELECTED}
+            SectionSetInstTypes ${SecPyBindings${PYVER}} 3
+
+            Py${PYVER}ExistsNot:
+            Pop $0
+        !endif
+    !endif
+!macroend
 
-        AskRegPath:
-        MessageBox MB_YESNO|MB_ICONQUESTION \
-            "You already have a copy of Python ${PYVER} installed in:$\r$\n$0$\r$\n$\r$\nPanda3D installs its own copy of Python ${PYVER}, which will install alongside your existing copy.  Would you like to make Panda's copy the default Python for your user account?" \
-            IDNO SkipRegPath
+Function .onInit
+    ${If} ${REGVIEW} = 64
+    ${AndIfNot} ${RunningX64}
+        MessageBox MB_OK|MB_ICONEXCLAMATION "You are attempting to install the 64-bit version of Panda3D on a 32-bit version of Windows.  Please download and install the 32-bit version of Panda3D instead."
+        Abort
+    ${EndIf}
 
-        RegPath:
-        WriteRegStr HKCU "Software\Python\PythonCore\${PYVER}\InstallPath" "" "$INSTDIR\python"
-        SkipRegPath:
+    !ifdef REGVIEW
+    SetRegView ${REGVIEW}
+    !endif
 
-    SectionEnd
+    ; We never check for 2.7; it is always enabled in Auto mode
+    !if "${REGVIEW}" == "32"
+        !insertmacro MaybeEnablePyBindingSection 3.5-32
+        !insertmacro MaybeEnablePyBindingSection 3.6-32
+        !insertmacro MaybeEnablePyBindingSection 3.7-32
+        !insertmacro MaybeEnablePyBindingSection 3.8-32
+        !insertmacro MaybeEnablePyBindingSection 3.9-32
+    !else
+        !insertmacro MaybeEnablePyBindingSection 3.5
+        !insertmacro MaybeEnablePyBindingSection 3.6
+        !insertmacro MaybeEnablePyBindingSection 3.7
+        !insertmacro MaybeEnablePyBindingSection 3.8
+        !insertmacro MaybeEnablePyBindingSection 3.9
     !endif
-SectionGroupEnd
+FunctionEnd
+
+Function .onSelChange
+    ; If someone selects any Python version, the "shared modules" must be on.
+    ${If} ${SectionIsPartiallySelected} ${SecGroupPython}
+        SectionGetFlags ${SecPyShared} $R0
+        IntOp $R0 $R0 | ${SF_SELECTED}
+        SectionSetFlags ${SecPyShared} $R0
+    ${EndIf}
+FunctionEnd
 
+!ifdef INCLUDE_PYVER
 Function ConfirmPythonSelection
     ; Check the current state of the "Python" section selection.
     SectionGetFlags ${SecPython} $R0
@@ -475,16 +549,14 @@ Function ConfirmPythonSelection
     StrCmp $R1 ${SF_SELECTED} SkipCheck 0
 
     ; Maybe the user just doesn't want Python support at all?
-    SectionGetFlags ${SecPyBindings} $R1
-    IntOp $R1 $R1 & ${SF_SELECTED}
-    StrCmp $R1 ${SF_SELECTED} 0 SkipCheck
+    !insertmacro SectionFlagIsSet ${SecGroupPython} ${SF_PSELECTED} 0 SkipCheck
 
     !ifdef REGVIEW
     SetRegView ${REGVIEW}
     !endif
 
     ; Check for a user installation of Python.
-    ReadRegStr $0 HKCU "Software\Python\PythonCore\${PYVER}\InstallPath" ""
+    ReadRegStr $0 HKCU "Software\Python\PythonCore\${INCLUDE_PYVER}\InstallPath" ""
     StrCmp $0 "$INSTDIR\python" CheckSystemWidePython 0
     StrCmp $0 "" CheckSystemWidePython 0
     IfFileExists "$0\ppython.exe" CheckSystemWidePython 0
@@ -492,7 +564,7 @@ Function ConfirmPythonSelection
 
     ; Check for a system-wide Python installation.
     CheckSystemWidePython:
-    ReadRegStr $0 HKLM "Software\Python\PythonCore\${PYVER}\InstallPath" ""
+    ReadRegStr $0 HKLM "Software\Python\PythonCore\${INCLUDE_PYVER}\InstallPath" ""
     StrCmp $0 "$INSTDIR\python" AskConfirmation 0
     StrCmp $0 "" AskConfirmation 0
     IfFileExists "$0\ppython.exe" AskConfirmation 0
@@ -502,7 +574,7 @@ Function ConfirmPythonSelection
     ; of a different Panda3D build.)  Ask the user if he's sure about this.
     AskConfirmation:
     MessageBox MB_YESNO|MB_ICONQUESTION \
-        "You do not appear to have a ${REGVIEW}-bit version of Python ${PYVER} installed.  Are you sure you don't want Panda to install a compatible copy of Python?$\r$\n$\r$\nIf you choose Yes, you will not be able to do Python development with Panda3D until you install a ${REGVIEW}-bit version of Python ${PYVER} and manually configure it to be able to use Panda3D." \
+        "You do not appear to have a ${REGVIEW}-bit version of Python ${INCLUDE_PYVER} installed.  Are you sure you don't want Panda to install a compatible copy of Python?$\r$\n$\r$\nIf you choose Yes, you will not be able to do Python development with Panda3D until you install a ${REGVIEW}-bit version of Python and install the bindings for this version." \
         IDYES SkipCheck
 
     ; User clicked no, so re-enable the select box and abort.
@@ -512,9 +584,10 @@ Function ConfirmPythonSelection
 
     SkipCheck:
 FunctionEnd
+!endif
 
 Section "C++ support" SecHeadersLibs
-    SectionIn 1
+    SectionIn 1 2
 
     SetDetailsPrint both
     DetailPrint "Installing header files..."
@@ -533,7 +606,7 @@ SectionEnd
 
 !ifdef HAVE_SAMPLES
 Section "Sample programs" SecSamples
-    SectionIn 1
+    SectionIn 1 2
 
     ; Necessary for proper start menu shortcut installation
     SetShellVarContext current
@@ -600,7 +673,7 @@ SectionEnd
 
 !ifdef HAVE_MAX_PLUGINS
 Section "3ds Max plug-ins" SecMaxPlugins
-    SectionIn 1 3
+    SectionIn 1 2
 
     SetDetailsPrint both
     DetailPrint "Installing Autodesk 3ds Max plug-ins..."
@@ -615,7 +688,7 @@ SectionEnd
 
 !ifdef HAVE_MAYA_PLUGINS
 Section "Maya plug-ins" SecMayaPlugins
-    SectionIn 1 3
+    SectionIn 1 2
 
     SetDetailsPrint both
     DetailPrint "Installing Autodesk Maya plug-ins..."
@@ -738,17 +811,30 @@ Section Uninstall
     DeleteRegKey HKCU "Software\Classes\Panda3D.Multifile\DefaultIcon"
     DeleteRegKey HKCU "Software\Classes\Panda3D.Multifile\shell"
 
-    ReadRegStr $0 HKLM "Software\Python\PythonCore\${PYVER}\InstallPath" ""
-    StrCmp $0 "$INSTDIR\python" 0 +2
-    DeleteRegKey HKLM "Software\Python\PythonCore\${PYVER}"
+    !ifdef INCLUDE_PYVER
+        ReadRegStr $0 HKLM "Software\Python\PythonCore\${INCLUDE_PYVER}\InstallPath" ""
+        StrCmp $0 "$INSTDIR\python" 0 +2
+        DeleteRegKey HKLM "Software\Python\PythonCore\${INCLUDE_PYVER}"
 
-    ReadRegStr $0 HKCU "Software\Python\PythonCore\${PYVER}\InstallPath" ""
-    StrCmp $0 "$INSTDIR\python" 0 +2
-    DeleteRegKey HKCU "Software\Python\PythonCore\${PYVER}"
+        ReadRegStr $0 HKCU "Software\Python\PythonCore\${INCLUDE_PYVER}\InstallPath" ""
+        StrCmp $0 "$INSTDIR\python" 0 +2
+        DeleteRegKey HKCU "Software\Python\PythonCore\${INCLUDE_PYVER}"
+    !endif
 
-    ReadRegStr $0 HKCU "Software\Python\PythonCore\${PYVER}\PythonPath\Panda3D" ""
-    StrCmp $0 "$INSTDIR" 0 +2
-    DeleteRegKey HKCU "Software\Python\PythonCore\${PYVER}\PythonPath\Panda3D"
+    !insertmacro RemovePythonPath 2.7
+    !if "${REGVIEW}" == "32"
+        !insertmacro RemovePythonPath 3.5-32
+        !insertmacro RemovePythonPath 3.6-32
+        !insertmacro RemovePythonPath 3.7-32
+        !insertmacro RemovePythonPath 3.8-32
+        !insertmacro RemovePythonPath 3.9-32
+    !else
+        !insertmacro RemovePythonPath 3.5
+        !insertmacro RemovePythonPath 3.6
+        !insertmacro RemovePythonPath 3.7
+        !insertmacro RemovePythonPath 3.8
+        !insertmacro RemovePythonPath 3.9
+    !endif
 
     SetDetailsPrint both
     DetailPrint "Deleting files..."
@@ -813,8 +899,23 @@ SectionEnd
     !insertmacro MUI_DESCRIPTION_TEXT ${SecRocket} $(DESC_SecRocket)
   !endif
   !insertmacro MUI_DESCRIPTION_TEXT ${SecTools} $(DESC_SecTools)
-  !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings} $(DESC_SecPyBindings)
-  !ifdef HAVE_PYTHON
+  !insertmacro MUI_DESCRIPTION_TEXT ${SecGroupPython} $(DESC_SecGroupPython)
+  !insertmacro MUI_DESCRIPTION_TEXT ${SecPyShared} $(DESC_SecPyShared)
+  !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings2.7} $(DESC_SecPyBindings2.7)
+  !if "${REGVIEW}" == "32"
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.5-32} $(DESC_SecPyBindings3.5-32)
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.6-32} $(DESC_SecPyBindings3.6-32)
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.7-32} $(DESC_SecPyBindings3.7-32)
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.8-32} $(DESC_SecPyBindings3.8-32)
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.9-32} $(DESC_SecPyBindings3.9-32)
+  !else
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.5} $(DESC_SecPyBindings3.5)
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.6} $(DESC_SecPyBindings3.6)
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.7} $(DESC_SecPyBindings3.7)
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.8} $(DESC_SecPyBindings3.8)
+    !insertmacro MUI_DESCRIPTION_TEXT ${SecPyBindings3.9} $(DESC_SecPyBindings3.9)
+  !endif
+  !ifdef INCLUDE_PYVER
     !insertmacro MUI_DESCRIPTION_TEXT ${SecPython} $(DESC_SecPython)
   !endif
   !insertmacro MUI_DESCRIPTION_TEXT ${SecHeadersLibs} $(DESC_SecHeadersLibs)

+ 29 - 23
makepanda/installpanda.py

@@ -13,10 +13,6 @@ from distutils.sysconfig import get_python_lib
 from optparse import OptionParser
 from makepandacore import *
 
-def python_sitepackages_path():
-    from distutils.sysconfig import get_python_lib
-    return get_python_lib(1)
-PYTHON_SITEPACKAGES=python_sitepackages_path()
 
 MIME_INFO = (
   ("egg", "model/x-egg", "EGG model file", "pview"),
@@ -153,18 +149,11 @@ def GetLibDir():
 
     return "lib"
 
-def InstallPanda(destdir="", prefix="/usr", outputdir="built", libdir=GetLibDir()):
+def InstallPanda(destdir="", prefix="/usr", outputdir="built", libdir=GetLibDir(), python_versions=[]):
     if (not prefix.startswith("/")):
         prefix = "/" + prefix
     libdir = prefix + "/" + libdir
 
-    # Determine the location of the Python executable and site-packages dir.
-    PPATH = get_python_lib(1)
-    if os.path.islink(sys.executable):
-        PEXEC = os.path.join(os.path.dirname(sys.executable), os.readlink(sys.executable))
-    else:
-        PEXEC = sys.executable
-
     # Create the directory structure that we will be putting our files in.
     oscmd("mkdir -m 0755 -p "+destdir+prefix+"/bin")
     oscmd("mkdir -m 0755 -p "+destdir+prefix+"/include")
@@ -174,8 +163,10 @@ def InstallPanda(destdir="", prefix="/usr", outputdir="built", libdir=GetLibDir(
     oscmd("mkdir -m 0755 -p "+destdir+prefix+"/share/application-registry")
     oscmd("mkdir -m 0755 -p "+destdir+prefix+"/share/applications")
     oscmd("mkdir -m 0755 -p "+destdir+libdir+"/panda3d")
-    oscmd("mkdir -m 0755 -p "+destdir+PPATH)
-    oscmd("mkdir -m 0755 -p "+destdir+PPATH+"/panda3d")
+
+    for python_version in python_versions:
+        oscmd("mkdir -m 0755 -p "+destdir+python_version["purelib"])
+        oscmd("mkdir -m 0755 -p "+destdir+python_version["platlib"]+"/panda3d")
 
     if (sys.platform.startswith("freebsd")):
         oscmd("mkdir -m 0755 -p "+destdir+prefix+"/etc")
@@ -201,10 +192,12 @@ def InstallPanda(destdir="", prefix="/usr", outputdir="built", libdir=GetLibDir(
     if os.path.isdir(outputdir+"/Pmw"):      oscmd("cp -R "+outputdir+"/Pmw     "+destdir+prefix+"/share/panda3d/")
     if os.path.isdir(outputdir+"/plugins"):  oscmd("cp -R "+outputdir+"/plugins "+destdir+prefix+"/share/panda3d/")
 
-    suffix = GetExtensionSuffix()
-    for base in os.listdir(outputdir + "/panda3d"):
-        if base.endswith(".py") or (base.endswith(suffix) and '.' not in base[:-len(suffix)]):
-            oscmd("cp "+outputdir+"/panda3d/"+base+" "+destdir+PPATH+"/panda3d/"+base)
+    for python_version in python_versions:
+        for base in os.listdir(outputdir + "/panda3d"):
+            suffix = python_version["ext_suffix"]
+            platlib = python_version["platlib"]
+            if base.endswith(".py") or (base.endswith(suffix) and '.' not in base[:-len(suffix)]):
+                oscmd("cp "+outputdir+"/panda3d/"+base+" "+destdir+platlib+"/panda3d/"+base)
 
     WriteMimeFile(destdir+prefix+"/share/mime-info/panda3d.mime", MIME_INFO)
     WriteKeysFile(destdir+prefix+"/share/mime-info/panda3d.keys", MIME_INFO)
@@ -214,19 +207,28 @@ def InstallPanda(destdir="", prefix="/usr", outputdir="built", libdir=GetLibDir(
         oscmd("cp makepanda/pview.desktop "+destdir+prefix+"/share/applications/pview.desktop")
 
     oscmd("cp doc/ReleaseNotes                  "+destdir+prefix+"/share/panda3d/ReleaseNotes")
-    oscmd("echo '"+prefix+"/share/panda3d' >    "+destdir+PPATH+"/panda3d.pth")
-    oscmd("echo '"+libdir+"/panda3d'>>   "+destdir+PPATH+"/panda3d.pth")
+
+    for python_version in python_versions:
+        pth_file = python_version["purelib"] + "/panda3d.pth"
+        oscmd("echo '"+prefix+"/share/panda3d' > "+destdir+pth_file)
+
+        if os.path.isdir(outputdir+"/panda3d.dist-info"):
+            oscmd("cp -R "+outputdir+"/panda3d.dist-info "+destdir+python_version["platlib"])
+
     if (sys.platform.startswith("freebsd")):
         oscmd("echo '"+libdir+"/panda3d'>    "+destdir+"/usr/local/libdata/ldconfig/panda3d")
     else:
         oscmd("echo '"+libdir+"/panda3d'>    "+destdir+"/etc/ld.so.conf.d/panda3d.conf")
 
-    oscmd("cp "+outputdir+"/bin/*               "+destdir+prefix+"/bin/")
     for base in os.listdir(outputdir+"/lib"):
         if (not base.endswith(".a")) or base == "libp3pystub.a":
             # We really need to specify -R in order not to follow symlinks on non-GNU
             oscmd("cp -R -P "+outputdir+"/lib/"+base+" "+destdir+libdir+"/panda3d/"+base)
 
+    for base in os.listdir(outputdir+"/bin"):
+        if not base.startswith("deploy-stub"):
+            oscmd("cp -R -P "+outputdir+"/bin/"+base+" "+destdir+prefix+"/bin/"+base)
+
     DeleteVCS(destdir+prefix+"/share/panda3d")
     DeleteBuildFiles(destdir+prefix+"/share/panda3d")
     DeleteEmptyDirs(destdir+prefix+"/share/panda3d")
@@ -300,6 +302,8 @@ if (__name__ == "__main__"):
     if (destdir != "" and not os.path.isdir(destdir)):
         exit("Directory '%s' does not exist!" % destdir)
 
+    SetOutputDir(options.outputdir)
+
     if options.verbose:
         SetVerbose(True)
 
@@ -308,6 +312,8 @@ if (__name__ == "__main__"):
         InstallRuntime(destdir = destdir, prefix = options.prefix, outputdir = options.outputdir)
     else:
         print("Installing Panda3D SDK into " + destdir + options.prefix)
-        InstallPanda(destdir = destdir, prefix = options.prefix, outputdir = options.outputdir)
+        InstallPanda(destdir=destdir,
+                     prefix=options.prefix,
+                     outputdir=options.outputdir,
+                     python_versions=ReadPythonVersionInfoFile())
     print("Installation finished!")
-

+ 1059 - 0
makepanda/makepackage.py

@@ -0,0 +1,1059 @@
+#!/usr/bin/env python
+
+from makepandacore import *
+from installpanda import *
+import sys
+import os
+import shutil
+
+
+INSTALLER_DEB_FILE = """
+Package: panda3dMAJOR
+Version: VERSION
+Section: libdevel
+Priority: optional
+Architecture: ARCH
+Essential: no
+Depends: DEPENDS
+Recommends: RECOMMENDS
+Provides: PROVIDES
+Conflicts: PROVIDES
+Replaces: PROVIDES
+Maintainer: rdb <[email protected]>
+Installed-Size: INSTSIZE
+Description: Panda3D free 3D engine SDK
+ Panda3D is a game engine which includes graphics, audio, I/O, collision detection, and other abilities relevant to the creation of 3D games. Panda3D is open source and free software under the revised BSD license, and can be used for both free and commercial game development at no financial cost.
+ Panda3D's intended game-development language is Python. The engine itself is written in C++, and utilizes an automatic wrapper-generator to expose the complete functionality of the engine in a Python interface.
+ .
+ This package contains the SDK for development with Panda3D.
+
+"""
+
+RUNTIME_INSTALLER_DEB_FILE = """
+Package: panda3d-runtime
+Version: VERSION
+Section: web
+Priority: optional
+Architecture: ARCH
+Essential: no
+Depends: DEPENDS
+Provides: panda3d-runtime
+Maintainer: rdb <[email protected]>
+Installed-Size: INSTSIZE
+Description: Runtime binary and browser plugin for the Panda3D Game Engine
+ This package contains the runtime distribution and browser plugin of the Panda3D engine. It allows you view webpages that contain Panda3D content and to run games created with Panda3D that are packaged as .p3d file.
+
+"""
+
+
+# We're not putting "python" in the "Requires" field,
+# since the rpm-based distros don't have a common
+# naming for the Python package.
+INSTALLER_SPEC_FILE = """
+Summary: The Panda3D free 3D engine SDK
+Name: panda3d
+Version: VERSION
+Release: RPMRELEASE
+License: BSD License
+Group: Development/Libraries
+BuildRoot: PANDASOURCE/targetroot
+%description
+Panda3D is a game engine which includes graphics, audio, I/O, collision detection, and other abilities relevant to the creation of 3D games. Panda3D is open source and free software under the revised BSD license, and can be used for both free and commercial game development at no financial cost.
+Panda3D's intended game-development language is Python. The engine itself is written in C++, and utilizes an automatic wrapper-generator to expose the complete functionality of the engine in a Python interface.
+
+This package contains the SDK for development with Panda3D, install panda3d-runtime for the runtime files.
+%post
+/sbin/ldconfig
+%postun
+/sbin/ldconfig
+%files
+%defattr(-,root,root)
+/etc/Confauto.prc
+/etc/Config.prc
+/usr/share/panda3d
+/etc/ld.so.conf.d/panda3d.conf
+/usr/%_lib/panda3d
+/usr/include/panda3d
+"""
+INSTALLER_SPEC_FILE_PVIEW = \
+"""/usr/share/applications/pview.desktop
+/usr/share/mime-info/panda3d.mime
+/usr/share/mime-info/panda3d.keys
+/usr/share/mime/packages/panda3d.xml
+/usr/share/application-registry/panda3d.applications
+"""
+
+RUNTIME_INSTALLER_SPEC_FILE = """
+Summary: Runtime binary and browser plugin for the Panda3D Game Engine
+Name: panda3d-runtime
+Version: VERSION
+Release: RPMRELEASE
+License: BSD License
+Group: Productivity/Graphics/Other
+BuildRoot: PANDASOURCE/targetroot
+%description
+This package contains the runtime distribution and browser plugin of the Panda3D engine. It allows you view webpages that contain Panda3D content and to run games created with Panda3D that are packaged as .p3d file.
+%files
+%defattr(-,root,root)
+/usr/bin/panda3d
+/usr/%_lib/nppanda3d.so
+/usr/%_lib/mozilla/plugins/nppanda3d.so
+/usr/%_lib/mozilla-firefox/plugins/nppanda3d.so
+/usr/%_lib/xulrunner-addons/plugins/nppanda3d.so
+/usr/share/mime-info/panda3d-runtime.mime
+/usr/share/mime-info/panda3d-runtime.keys
+/usr/share/mime/packages/panda3d-runtime.xml
+/usr/share/application-registry/panda3d-runtime.applications
+/usr/share/applications/*.desktop
+"""
+
+# plist file for Mac OSX
+Info_plist = """<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+  <key>CFBundleIdentifier</key>
+  <string>{package_id}</string>
+  <key>CFBundleShortVersionString</key>
+  <string>{version}</string>
+  <key>IFPkgFlagRelocatable</key>
+  <false/>
+  <key>IFPkgFlagAuthorizationAction</key>
+  <string>RootAuthorization</string>
+  <key>IFPkgFlagAllowBackRev</key>
+  <true/>
+</dict>
+</plist>
+"""
+
+# FreeBSD pkg-descr
+INSTALLER_PKG_DESCR_FILE = """
+Panda3D is a game engine which includes graphics, audio, I/O, collision detection, and other abilities relevant to the creation of 3D games. Panda3D is open source and free software under the revised BSD license, and can be used for both free and commercial game development at no financial cost.
+Panda3D's intended game-development language is Python. The engine itself is written in C++, and utilizes an automatic wrapper-generator to expose the complete functionality of the engine in a Python interface.
+
+This package contains the SDK for development with Panda3D, install panda3d-runtime for the runtime files.
+
+WWW: https://www.panda3d.org/
+"""
+
+# FreeBSD pkg-descr
+RUNTIME_INSTALLER_PKG_DESCR_FILE = """
+Runtime binary and browser plugin for the Panda3D Game Engine
+
+This package contains the runtime distribution and browser plugin of the Panda3D engine. It allows you view webpages that contain Panda3D content and to run games created with Panda3D that are packaged as .p3d file.
+
+WWW: https://www.panda3d.org/
+"""
+
+# FreeBSD PKG Manifest template file
+INSTALLER_PKG_MANIFEST_FILE = """
+name: NAME
+version: VERSION
+arch: ARCH
+origin: ORIGIN
+comment: "Panda3D free 3D engine SDK"
+www: https://www.panda3d.org
+maintainer: rdb <[email protected]>
+prefix: /usr/local
+flatsize: INSTSIZEMB
+deps: {DEPENDS}
+"""
+
+
+def MakeInstallerNSIS(version, file, title, installdir, runtime=False, compressor="lzma", **kwargs):
+    outputdir = GetOutputDir()
+
+    if os.path.isfile(file):
+        os.remove(file)
+    elif os.path.isdir(file):
+        shutil.rmtree(file)
+
+    if GetTargetArch() == 'x64':
+        regview = '64'
+    else:
+        regview = '32'
+
+    if runtime:
+        # Invoke the make_installer script.
+        AddToPathEnv("PATH", outputdir + "\\bin")
+        AddToPathEnv("PATH", outputdir + "\\plugins")
+
+        cmd = sys.executable + " -B -u " + os.path.join("direct", "src", "plugin_installer", "make_installer.py")
+        cmd += " --version %s --regview %s" % (version, regview)
+
+        if GetTargetArch() == 'x64':
+            cmd += " --install \"$PROGRAMFILES64\\Panda3D\" "
+        else:
+            cmd += " --install \"$PROGRAMFILES32\\Panda3D\" "
+
+        oscmd(cmd)
+        shutil.move(os.path.join("direct", "src", "plugin_installer", "p3d-setup.exe"), file)
+        return
+
+    print("Building "+title+" installer at %s" % (file))
+    if compressor != "lzma":
+        print("Note: you are using zlib, which is faster, but lzma gives better compression.")
+    if os.path.exists("nsis-output.exe"):
+        os.remove("nsis-output.exe")
+    WriteFile(outputdir+"/tmp/__init__.py", "")
+
+    nsis_defs = {
+        'COMPRESSOR': compressor,
+        'TITLE'     : title,
+        'INSTALLDIR': installdir,
+        'OUTFILE'   : '..\\' + file,
+        'BUILT'     : '..\\' + outputdir,
+        'SOURCE'    : '..',
+        'REGVIEW'   : regview,
+    }
+
+    # Are we shipping a version of Python?
+    if os.path.isfile(os.path.join(outputdir, "python", "python.exe")):
+        py_dlls = glob.glob(os.path.join(outputdir, "python", "python[0-9][0-9].dll")) \
+                + glob.glob(os.path.join(outputdir, "python", "python[0-9][0-9]_d.dll"))
+        assert py_dlls
+        py_dll = os.path.basename(py_dlls[0])
+        pyver = py_dll[6] + "." + py_dll[7]
+
+        if GetTargetArch() != 'x64':
+            pyver += '-32'
+
+        nsis_defs['INCLUDE_PYVER'] = pyver
+
+    if GetHost() == 'windows':
+        cmd = os.path.join(GetThirdpartyBase(), 'win-nsis', 'makensis') + ' /V2'
+        for item in nsis_defs.items():
+            cmd += ' /D%s="%s"' % item
+    else:
+        cmd = 'makensis -V2'
+        for item in nsis_defs.items():
+            cmd += ' -D%s="%s"' % item
+
+    cmd += ' "makepanda\\installer.nsi"'
+    oscmd(cmd)
+
+
+def MakeDebugSymbolArchive(zipname, dirname):
+    outputdir = GetOutputDir()
+
+    import zipfile
+    zip = zipfile.ZipFile(zipname, 'w', zipfile.ZIP_DEFLATED)
+
+    for fn in glob.glob(os.path.join(outputdir, 'bin', '*.pdb')):
+        zip.write(fn, dirname + '/bin/' + os.path.basename(fn))
+
+    for fn in glob.glob(os.path.join(outputdir, 'panda3d', '*.pdb')):
+        zip.write(fn, dirname + '/panda3d/' + os.path.basename(fn))
+
+    for fn in glob.glob(os.path.join(outputdir, 'plugins', '*.pdb')):
+        zip.write(fn, dirname + '/plugins/' + os.path.basename(fn))
+
+    for fn in glob.glob(os.path.join(outputdir, 'python', '*.pdb')):
+        zip.write(fn, dirname + '/python/' + os.path.basename(fn))
+
+    for fn in glob.glob(os.path.join(outputdir, 'python', 'DLLs', '*.pdb')):
+        zip.write(fn, dirname + '/python/DLLs/' + os.path.basename(fn))
+
+    zip.close()
+
+
+def MakeInstallerLinux(version, debversion=None, rpmrelease=1, runtime=False,
+                       python_versions=[], **kwargs):
+    outputdir = GetOutputDir()
+
+    # We pack Python 2 and Python 3, if we built with support for it.
+    python2_ver = None
+    python3_ver = None
+    install_python_versions = []
+
+    if not runtime:
+        # What's the system version of Python 3?
+        oscmd('python3 -V > "%s/tmp/python3_version.txt"' % (outputdir))
+        sys_python3_ver = '.'.join(ReadFile(outputdir + "/tmp/python3_version.txt").strip().split(' ')[1].split('.')[:2])
+
+        # Check that we built with support for these.
+        for version_info in python_versions:
+            if version_info["version"] == "2.7":
+                python2_ver = "2.7"
+                install_python_versions.append(version_info)
+            elif version_info["version"] == sys_python3_ver:
+                python3_ver = sys_python3_ver
+                install_python_versions.append(version_info)
+
+    major_version = '.'.join(version.split('.')[:2])
+    if not debversion:
+        debversion = version
+
+    # Clean and set up a directory to install Panda3D into
+    oscmd("rm -rf targetroot data.tar.gz control.tar.gz panda3d.spec")
+    oscmd("mkdir -m 0755 targetroot")
+
+    dpkg_present = False
+    if os.path.exists("/usr/bin/dpkg-architecture") and os.path.exists("/usr/bin/dpkg-deb"):
+        dpkg_present = True
+    rpmbuild_present = False
+    if os.path.exists("/usr/bin/rpmbuild"):
+        rpmbuild_present = True
+
+    if dpkg_present and rpmbuild_present:
+        Warn("both dpkg and rpmbuild present.")
+
+    if dpkg_present:
+        # Invoke installpanda.py to install it into a temporary dir
+        lib_dir = GetDebLibDir()
+        if runtime:
+            InstallRuntime(destdir="targetroot", prefix="/usr",
+                           outputdir=outputdir, libdir=lib_dir)
+        else:
+            InstallPanda(destdir="targetroot", prefix="/usr",
+                         outputdir=outputdir, libdir=lib_dir,
+                         python_versions=install_python_versions)
+            oscmd("chmod -R 755 targetroot/usr/share/panda3d")
+            oscmd("mkdir -m 0755 -p targetroot/usr/share/man/man1")
+            oscmd("install -m 0644 doc/man/*.1 targetroot/usr/share/man/man1/")
+
+        oscmd("dpkg --print-architecture > "+outputdir+"/tmp/architecture.txt")
+        pkg_arch = ReadFile(outputdir+"/tmp/architecture.txt").strip()
+        if runtime:
+            txt = RUNTIME_INSTALLER_DEB_FILE[1:]
+        else:
+            txt = INSTALLER_DEB_FILE[1:]
+        txt = txt.replace("VERSION", debversion).replace("ARCH", pkg_arch).replace("MAJOR", major_version)
+        txt = txt.replace("INSTSIZE", str(GetDirectorySize("targetroot") // 1024))
+
+        oscmd("mkdir -m 0755 -p targetroot/DEBIAN")
+        oscmd("cd targetroot && (find usr -type f -exec md5sum {} ;) > DEBIAN/md5sums")
+        if not runtime:
+            oscmd("cd targetroot && (find etc -type f -exec md5sum {} ;) >> DEBIAN/md5sums")
+            WriteFile("targetroot/DEBIAN/conffiles","/etc/Config.prc\n")
+        WriteFile("targetroot/DEBIAN/postinst","#!/bin/sh\necho running ldconfig\nldconfig\n")
+        oscmd("cp targetroot/DEBIAN/postinst targetroot/DEBIAN/postrm")
+
+        # Determine the package name and the locations that
+        # dpkg-shlibdeps should look in for executables.
+        pkg_version = debversion
+        if runtime:
+            pkg_name = "panda3d-runtime"
+            lib_pattern = "debian/%s/usr/%s/*.so" % (pkg_name, lib_dir)
+        else:
+            pkg_name = "panda3d" + major_version
+            lib_pattern = "debian/%s/usr/%s/panda3d/*.so*" % (pkg_name, lib_dir)
+        bin_pattern = "debian/%s/usr/bin/*" % (pkg_name)
+
+        # dpkg-shlibdeps looks in the debian/{pkg_name}/DEBIAN/shlibs directory
+        # and also expects a debian/control file, so we create this dummy set-up.
+        oscmd("mkdir targetroot/debian")
+        oscmd("ln -s .. targetroot/debian/" + pkg_name)
+        WriteFile("targetroot/debian/control", "")
+
+        dpkg_shlibdeps = "dpkg-shlibdeps"
+        if GetVerbose():
+            dpkg_shlibdeps += " -v"
+
+        if runtime:
+            # The runtime doesn't export any useful symbols, so just query the dependencies.
+            oscmd("cd targetroot && %(dpkg_shlibdeps)s -x%(pkg_name)s %(lib_pattern)s %(bin_pattern)s*" % locals())
+            depends = ReadFile("targetroot/debian/substvars").replace("shlibs:Depends=", "").strip()
+            recommends = ""
+            provides = "panda3d-runtime"
+        else:
+            pkg_name = "panda3d" + major_version
+            pkg_dir = "debian/panda3d" + major_version
+
+            # Generate a symbols file so that other packages can know which symbols we export.
+            oscmd("cd targetroot && dpkg-gensymbols -q -ODEBIAN/symbols -v%(pkg_version)s -p%(pkg_name)s -e%(lib_pattern)s" % locals())
+
+            # Library dependencies are required, binary dependencies are recommended.
+            # We explicitly exclude libphysx-extras since we don't want to depend on PhysX.
+            oscmd("cd targetroot && LD_LIBRARY_PATH=usr/%(lib_dir)s/panda3d %(dpkg_shlibdeps)s -Tdebian/substvars_dep --ignore-missing-info -x%(pkg_name)s -xlibphysx-extras %(lib_pattern)s" % locals())
+            oscmd("cd targetroot && LD_LIBRARY_PATH=usr/%(lib_dir)s/panda3d %(dpkg_shlibdeps)s -Tdebian/substvars_rec --ignore-missing-info -x%(pkg_name)s %(bin_pattern)s" % locals())
+
+            # Parse the substvars files generated by dpkg-shlibdeps.
+            depends = ReadFile("targetroot/debian/substvars_dep").replace("shlibs:Depends=", "").strip()
+            recommends = ReadFile("targetroot/debian/substvars_rec").replace("shlibs:Depends=", "").strip()
+            provides = "panda3d"
+
+            if python2_ver or python3_ver:
+                recommends += ", python-pmw"
+
+            if python2_ver:
+                depends += ", python%s" % (python2_ver)
+                recommends += ", python-wxversion"
+                recommends += ", python-tk (>= %s)" % (python2_ver)
+                provides += ", python2-panda3d"
+
+            if python3_ver:
+                depends += ", python%s" % (python3_ver)
+                recommends += ", python3-tk (>= %s)" % (python3_ver)
+                provides += ", python3-panda3d"
+
+            if not PkgSkip("NVIDIACG"):
+                depends += ", nvidia-cg-toolkit"
+
+        # Write back the dependencies, and delete the dummy set-up.
+        txt = txt.replace("DEPENDS", depends.strip(', '))
+        txt = txt.replace("RECOMMENDS", recommends.strip(', '))
+        txt = txt.replace("PROVIDES", provides.strip(', '))
+        WriteFile("targetroot/DEBIAN/control", txt)
+        oscmd("rm -rf targetroot/debian")
+
+        # Package it all up into a .deb file.
+        oscmd("chmod -R 755 targetroot/DEBIAN")
+        oscmd("chmod 644 targetroot/DEBIAN/control targetroot/DEBIAN/md5sums")
+        if not runtime:
+            oscmd("chmod 644 targetroot/DEBIAN/conffiles targetroot/DEBIAN/symbols")
+        oscmd("fakeroot dpkg-deb -b targetroot %s_%s_%s.deb" % (pkg_name, pkg_version, pkg_arch))
+
+    elif rpmbuild_present:
+        # Invoke installpanda.py to install it into a temporary dir
+        if runtime:
+            InstallRuntime(destdir="targetroot", prefix="/usr", outputdir=outputdir, libdir=GetRPMLibDir())
+        else:
+            InstallPanda(destdir="targetroot", prefix="/usr",
+                         outputdir=outputdir, libdir=GetRPMLibDir(),
+                         python_versions=install_python_versions)
+            oscmd("chmod -R 755 targetroot/usr/share/panda3d")
+
+        oscmd("rpm -E '%_target_cpu' > "+outputdir+"/tmp/architecture.txt")
+        arch = ReadFile(outputdir+"/tmp/architecture.txt").strip()
+        pandasource = os.path.abspath(os.getcwd())
+
+        if runtime:
+            txt = RUNTIME_INSTALLER_SPEC_FILE[1:]
+        else:
+            txt = INSTALLER_SPEC_FILE[1:]
+
+            # Add the MIME associations if we have pview
+            if not PkgSkip("PVIEW"):
+                txt += INSTALLER_SPEC_FILE_PVIEW
+
+            # Add the platform-specific Python directories.
+            dirs = set()
+            for version_info in install_python_versions:
+                dirs.add(version_info["platlib"])
+                dirs.add(version_info["purelib"])
+
+            for dir in dirs:
+                txt += dir + "\n"
+
+            # Add the binaries in /usr/bin explicitly to the spec file
+            for base in os.listdir(outputdir + "/bin"):
+                txt += "/usr/bin/%s\n" % (base)
+
+        # Write out the spec file.
+        txt = txt.replace("VERSION", version)
+        txt = txt.replace("RPMRELEASE", str(rpmrelease))
+        txt = txt.replace("PANDASOURCE", pandasource)
+        WriteFile("panda3d.spec", txt)
+
+        oscmd("fakeroot rpmbuild --define '_rpmdir "+pandasource+"' --buildroot '"+os.path.abspath("targetroot")+"' -bb panda3d.spec")
+        if runtime:
+            oscmd("mv "+arch+"/panda3d-runtime-"+version+"-"+rpmrelease+"."+arch+".rpm .")
+        else:
+            oscmd("mv "+arch+"/panda3d-"+version+"-"+rpmrelease+"."+arch+".rpm .")
+        oscmd("rm -rf "+arch, True)
+
+    else:
+        exit("To build an installer, either rpmbuild or dpkg-deb must be present on your system!")
+
+
+def MakeInstallerOSX(version, runtime=False, python_versions=[], **kwargs):
+    outputdir = GetOutputDir()
+
+    if runtime:
+        # Invoke the make_installer script.
+        AddToPathEnv("DYLD_LIBRARY_PATH", outputdir + "/plugins")
+        cmdstr = sys.executable + " "
+        if sys.version_info >= (2, 6):
+            cmdstr += "-B "
+
+        cmdstr += "direct/src/plugin_installer/make_installer.py --version %s" % version
+        oscmd(cmdstr)
+        return
+
+    dmg_name = "Panda3D-" + version
+    if len(python_versions) == 1 and not python_versions[0]["version"].startswith("2."):
+        dmg_name += "-py" + pyver
+    dmg_name += ".dmg"
+
+    if (os.path.isfile(dmg_name)): oscmd("rm -f %s" % dmg_name)
+    if (os.path.exists("dstroot")): oscmd("rm -rf dstroot")
+    if (os.path.exists("Panda3D-rw.dmg")): oscmd('rm -f Panda3D-rw.dmg')
+
+    oscmd("mkdir -p dstroot/base/Developer/Panda3D/lib")
+    oscmd("mkdir -p dstroot/base/Developer/Panda3D/etc")
+    oscmd("cp %s/etc/Config.prc           dstroot/base/Developer/Panda3D/etc/Config.prc" % outputdir)
+    oscmd("cp %s/etc/Confauto.prc         dstroot/base/Developer/Panda3D/etc/Confauto.prc" % outputdir)
+    oscmd("cp -R %s/models                dstroot/base/Developer/Panda3D/models" % outputdir)
+    oscmd("cp -R doc/LICENSE              dstroot/base/Developer/Panda3D/LICENSE")
+    oscmd("cp -R doc/ReleaseNotes         dstroot/base/Developer/Panda3D/ReleaseNotes")
+    oscmd("cp -R %s/Frameworks            dstroot/base/Developer/Panda3D/Frameworks" % outputdir)
+    if os.path.isdir(outputdir+"/plugins"):
+        oscmd("cp -R %s/plugins           dstroot/base/Developer/Panda3D/plugins" % outputdir)
+
+    # Libraries that shouldn't be in base, but are instead in other modules.
+    no_base_libs = ['libp3ffmpeg', 'libp3fmod_audio', 'libfmodex', 'libfmodexL']
+
+    for base in os.listdir(outputdir+"/lib"):
+        if not base.endswith(".a") and base.split('.')[0] not in no_base_libs:
+            libname = "dstroot/base/Developer/Panda3D/lib/" + base
+            # We really need to specify -R in order not to follow symlinks
+            # On OSX, just specifying -P is not enough to do that.
+            oscmd("cp -R -P " + outputdir + "/lib/" + base + " " + libname)
+
+    oscmd("mkdir -p dstroot/tools/Developer/Panda3D/bin")
+    oscmd("mkdir -p dstroot/tools/Developer/Tools")
+    oscmd("ln -s ../Panda3D/bin dstroot/tools/Developer/Tools/Panda3D")
+    oscmd("mkdir -p dstroot/tools/etc/paths.d")
+    # Trailing newline is important, works around a bug in OSX
+    WriteFile("dstroot/tools/etc/paths.d/Panda3D", "/Developer/Panda3D/bin\n")
+
+    oscmd("mkdir -m 0755 -p dstroot/tools/usr/local/share/man/man1")
+    oscmd("install -m 0644 doc/man/*.1 dstroot/tools/usr/local/share/man/man1/")
+
+    for base in os.listdir(outputdir+"/bin"):
+        if not base.startswith("deploy-stub"):
+            binname = "dstroot/tools/Developer/Panda3D/bin/" + base
+            # OSX needs the -R argument to copy symbolic links correctly, it doesn't have -d. How weird.
+            oscmd("cp -R " + outputdir + "/bin/" + base + " " + binname)
+
+    if python_versions:
+        # Let's only write a ppython link if there is only one Python version.
+        if len(python_versions) == 1:
+            oscmd("mkdir -p dstroot/pythoncode/usr/local/bin")
+            oscmd("ln -s %s dstroot/pythoncode/usr/local/bin/ppython" % (python_versions[0]["executable"]))
+
+        oscmd("mkdir -p dstroot/pythoncode/Developer/Panda3D/panda3d")
+        oscmd("cp -R %s/pandac                dstroot/pythoncode/Developer/Panda3D/pandac" % outputdir)
+        oscmd("cp -R %s/direct                dstroot/pythoncode/Developer/Panda3D/direct" % outputdir)
+        oscmd("cp -R %s/*.so                  dstroot/pythoncode/Developer/Panda3D/" % outputdir, True)
+        oscmd("cp -R %s/*.py                  dstroot/pythoncode/Developer/Panda3D/" % outputdir, True)
+        if os.path.isdir(outputdir+"/Pmw"):
+            oscmd("cp -R %s/Pmw               dstroot/pythoncode/Developer/Panda3D/Pmw" % outputdir)
+
+        for base in os.listdir(outputdir+"/panda3d"):
+            if base.endswith('.py'):
+                libname = "dstroot/pythoncode/Developer/Panda3D/panda3d/" + base
+                oscmd("cp -R " + outputdir + "/panda3d/" + base + " " + libname)
+
+    for version_info in python_versions:
+        pyver = version_info["version"]
+        oscmd("mkdir -p dstroot/pybindings%s/Library/Python/%s/site-packages" % (pyver, pyver))
+        oscmd("mkdir -p dstroot/pybindings%s/Developer/Panda3D/panda3d" % (pyver))
+
+        # Copy over extension modules.
+        suffix = version_info["ext_suffix"]
+        for base in os.listdir(outputdir+"/panda3d"):
+            if base.endswith(suffix) and '.' not in base[:-len(suffix)]:
+                libname = "dstroot/pybindings%s/Developer/Panda3D/panda3d/%s" % (pyver, base)
+                # We really need to specify -R in order not to follow symlinks
+                # On OSX, just specifying -P is not enough to do that.
+                oscmd("cp -R -P " + outputdir + "/panda3d/" + base + " " + libname)
+
+        # Write a .pth file.
+        oscmd("mkdir -p dstroot/pybindings%s/Library/Python/%s/site-packages" % (pyver, pyver))
+        WriteFile("dstroot/pybindings%s/Library/Python/%s/site-packages/Panda3D.pth" % (pyver, pyver), "/Developer/Panda3D")
+
+        # Copy over panda3d.dist-info directory.
+        if os.path.isdir(outputdir + "/panda3d.dist-info"):
+            oscmd("cp -R %s/panda3d.dist-info dstroot/pybindings%s/Library/Python/%s/site-packages/" % (outputdir, pyver, pyver))
+
+    if not PkgSkip("FFMPEG"):
+        oscmd("mkdir -p dstroot/ffmpeg/Developer/Panda3D/lib")
+        oscmd("cp -R %s/lib/libp3ffmpeg.* dstroot/ffmpeg/Developer/Panda3D/lib/" % outputdir)
+
+    #if not PkgSkip("OPENAL"):
+    #    oscmd("mkdir -p dstroot/openal/Developer/Panda3D/lib")
+    #    oscmd("cp -R %s/lib/libp3openal_audio.* dstroot/openal/Developer/Panda3D/lib/" % outputdir)
+
+    if not PkgSkip("FMODEX"):
+        oscmd("mkdir -p dstroot/fmodex/Developer/Panda3D/lib")
+        oscmd("cp -R %s/lib/libp3fmod_audio.* dstroot/fmodex/Developer/Panda3D/lib/" % outputdir)
+        oscmd("cp -R %s/lib/libfmodex* dstroot/fmodex/Developer/Panda3D/lib/" % outputdir)
+
+    oscmd("mkdir -p dstroot/headers/Developer/Panda3D/lib")
+    oscmd("cp -R %s/include               dstroot/headers/Developer/Panda3D/include" % outputdir)
+    if os.path.isfile(outputdir + "/lib/libp3pystub.a"):
+        oscmd("cp -R -P %s/lib/libp3pystub.a dstroot/headers/Developer/Panda3D/lib/" % outputdir)
+
+    if os.path.isdir("samples"):
+        oscmd("mkdir -p dstroot/samples/Developer/Examples/Panda3D")
+        oscmd("cp -R samples/* dstroot/samples/Developer/Examples/Panda3D/")
+
+    DeleteVCS("dstroot")
+    DeleteBuildFiles("dstroot")
+
+    # Compile Python files.  Do this *after* the DeleteVCS step, above, which
+    # deletes __pycache__ directories.
+    for version_info in python_versions:
+        if os.path.isdir("dstroot/pythoncode/Developer/Panda3D/Pmw"):
+            oscmd("%s -m compileall -q -f -d /Developer/Panda3D/Pmw dstroot/pythoncode/Developer/Panda3D/Pmw" % (version_info["executable"]), True)
+        oscmd("%s -m compileall -q -f -d /Developer/Panda3D/direct dstroot/pythoncode/Developer/Panda3D/direct" % (version_info["executable"]))
+        oscmd("%s -m compileall -q -f -d /Developer/Panda3D/pandac dstroot/pythoncode/Developer/Panda3D/pandac" % (version_info["executable"]))
+        oscmd("%s -m compileall -q -f -d /Developer/Panda3D/panda3d dstroot/pythoncode/Developer/Panda3D/panda3d" % (version_info["executable"]))
+
+    oscmd("chmod -R 0775 dstroot/*")
+    # We need to be root to perform a chown. Bleh.
+    # Fortunately PackageMaker does it for us, on 10.5 and above.
+    #oscmd("chown -R root:admin dstroot/*", True)
+
+    oscmd("mkdir -p dstroot/Panda3D/Panda3D.mpkg/Contents/Packages/")
+    oscmd("mkdir -p dstroot/Panda3D/Panda3D.mpkg/Contents/Resources/en.lproj/")
+
+    pkgs = ["base", "tools", "headers"]
+    if python_versions:
+        pkgs.append("pythoncode")
+    for version_info in python_versions:
+        pkgs.append("pybindings" + version_info["version"])
+    if not PkgSkip("FFMPEG"):    pkgs.append("ffmpeg")
+    #if not PkgSkip("OPENAL"):    pkgs.append("openal")
+    if not PkgSkip("FMODEX"):    pkgs.append("fmodex")
+    if os.path.isdir("samples"): pkgs.append("samples")
+    for pkg in pkgs:
+        identifier = "org.panda3d.panda3d.%s.pkg" % pkg
+        plist = open("/tmp/Info_plist", "w")
+        plist.write(Info_plist.format(package_id=identifier, version=version))
+        plist.close()
+        if not os.path.isdir("dstroot/" + pkg):
+            os.makedirs("dstroot/" + pkg)
+
+        if os.path.exists("/usr/bin/pkgbuild"):
+            # This new package builder is used in Lion and above.
+            cmd = '/usr/bin/pkgbuild --identifier ' + identifier + ' --version ' + version + ' --root dstroot/' + pkg + '/ dstroot/Panda3D/Panda3D.mpkg/Contents/Packages/' + pkg + '.pkg'
+
+        # In older versions, we use PackageMaker.  Apple keeps changing its location.
+        elif os.path.exists("/Developer/usr/bin/packagemaker"):
+            cmd = '/Developer/usr/bin/packagemaker --info /tmp/Info_plist --version ' + version + ' --out dstroot/Panda3D/Panda3D.mpkg/Contents/Packages/' + pkg + '.pkg ' + target + ' --domain system --root dstroot/' + pkg + '/ --no-relocate'
+        elif os.path.exists("/Applications/Xcode.app/Contents/Applications/PackageMaker.app/Contents/MacOS/PackageMaker"):
+            cmd = '/Applications/Xcode.app/Contents/Applications/PackageMaker.app/Contents/MacOS/PackageMaker --info /tmp/Info_plist --version ' + version + ' --out dstroot/Panda3D/Panda3D.mpkg/Contents/Packages/' + pkg + '.pkg ' + target + ' --domain system --root dstroot/' + pkg + '/ --no-relocate'
+        elif os.path.exists("/Developer/Tools/PackageMaker.app/Contents/MacOS/PackageMaker"):
+            cmd = '/Developer/Tools/PackageMaker.app/Contents/MacOS/PackageMaker --info /tmp/Info_plist --version ' + version + ' --out dstroot/Panda3D/Panda3D.mpkg/Contents/Packages/' + pkg + '.pkg ' + target + ' --domain system --root dstroot/' + pkg + '/ --no-relocate'
+        elif os.path.exists("/Developer/Tools/packagemaker"):
+            cmd = '/Developer/Tools/packagemaker -build -f dstroot/' + pkg + '/ -p dstroot/Panda3D/Panda3D.mpkg/Contents/Packages/' + pkg + '.pkg -i /tmp/Info_plist'
+        elif os.path.exists("/Applications/PackageMaker.app/Contents/MacOS/PackageMaker"):
+            cmd = '/Applications/PackageMaker.app/Contents/MacOS/PackageMaker --info /tmp/Info_plist --version ' + version + ' --out dstroot/Panda3D/Panda3D.mpkg/Contents/Packages/' + pkg + '.pkg ' + target + ' --domain system --root dstroot/' + pkg + '/ --no-relocate'
+        else:
+            exit("Neither pkgbuild nor PackageMaker could be found!")
+        oscmd(cmd)
+
+    if os.path.isfile("/tmp/Info_plist"):
+        oscmd("rm -f /tmp/Info_plist")
+
+    # Now that we've built all of the individual packages, build the metapackage.
+    dist = open("dstroot/Panda3D/Panda3D.mpkg/Contents/distribution.dist", "w")
+    dist.write('<?xml version="1.0" encoding="utf-8"?>\n')
+    dist.write('<installer-script minSpecVersion="1.000000" authoringTool="com.apple.PackageMaker" authoringToolVersion="3.0.3" authoringToolBuild="174">\n')
+    dist.write('    <title>Panda3D SDK %s</title>\n' % (version))
+    dist.write('    <options customize="always" allow-external-scripts="no" rootVolumeOnly="false"/>\n')
+    dist.write('    <license language="en" mime-type="text/plain">%s</license>\n' % ReadFile("doc/LICENSE"))
+    dist.write('    <script>\n')
+    dist.write('    function isPythonVersionInstalled(version) {\n')
+    dist.write('        return system.files.fileExistsAtPath("/usr/bin/python" + version)\n')
+    dist.write('            || system.files.fileExistsAtPath("/usr/local/bin/python" + version)\n')
+    dist.write('            || system.files.fileExistsAtPath("/opt/local/bin/python" + version)\n')
+    dist.write('            || system.files.fileExistsAtPath("/sw/bin/python" + version)\n')
+    dist.write('            || system.files.fileExistsAtPath("/System/Library/Frameworks/Python.framework/Versions/" + version + "/bin/python")\n')
+    dist.write('            || system.files.fileExistsAtPath("/Library/Frameworks/Python.framework/Versions/" + version + "/bin/python");\n')
+    dist.write('    }\n')
+    dist.write('    </script>\n')
+    dist.write('    <choices-outline>\n')
+    dist.write('        <line choice="base"/>\n')
+    if python_versions:
+        dist.write('        <line choice="pythoncode">\n')
+        for version_info in sorted(python_versions, key=lambda info:info["version"], reverse=True):
+            dist.write('            <line choice="pybindings%s"/>\n' % (version_info["version"]))
+        dist.write('        </line>\n')
+    dist.write('        <line choice="tools"/>\n')
+    if os.path.isdir("samples"):
+        dist.write('        <line choice="samples"/>\n')
+    if not PkgSkip("FFMPEG"):
+        dist.write('        <line choice="ffmpeg"/>\n')
+    if not PkgSkip("FMODEX"):
+        dist.write('        <line choice="fmodex"/>\n')
+    dist.write('        <line choice="headers"/>\n')
+    dist.write('    </choices-outline>\n')
+    dist.write('    <choice id="base" title="Panda3D Base Installation" description="This package contains the Panda3D libraries, configuration files and models/textures that are needed to use Panda3D.&#10;&#10;Location: /Developer/Panda3D/" start_enabled="false">\n')
+    dist.write('        <pkg-ref id="org.panda3d.panda3d.base.pkg"/>\n')
+    dist.write('    </choice>\n')
+    dist.write('    <choice id="tools" title="Tools" tooltip="Useful tools and model converters to help with Panda3D development" description="This package contains the various utilities that ship with Panda3D, including packaging tools, model converters, and many more.&#10;&#10;Location: /Developer/Panda3D/bin/">\n')
+    dist.write('        <pkg-ref id="org.panda3d.panda3d.tools.pkg"/>\n')
+    dist.write('    </choice>\n')
+
+    if python_versions:
+        dist.write('    <choice id="pythoncode" title="Python Support" tooltip="Python bindings for the Panda3D libraries" description="This package contains the \'direct\', \'pandac\' and \'panda3d\' python packages that are needed to do Python development with Panda3D.&#10;&#10;Location: /Developer/Panda3D/">\n')
+        dist.write('        <pkg-ref id="org.panda3d.panda3d.pythoncode.pkg"/>\n')
+        dist.write('    </choice>\n')
+
+    for version_info in python_versions:
+        pyver = version_info["version"]
+        if pyver == "2.7":
+            # Always install Python 2.7 by default; it's included on macOS.
+            cond = "true"
+        else:
+            cond = "isPythonVersionInstalled('%s')" % (pyver)
+        dist.write('    <choice id="pybindings%s" start_selected="%s" title="Python %s Bindings" tooltip="Python bindings for the Panda3D libraries" description="Support for Python %s.">\n' % (pyver, cond, pyver, pyver))
+        dist.write('        <pkg-ref id="org.panda3d.panda3d.pybindings%s.pkg"/>\n' % (pyver))
+        dist.write('    </choice>\n')
+
+    if not PkgSkip("FFMPEG"):
+        dist.write('    <choice id="ffmpeg" title="FFMpeg Plug-In" tooltip="FFMpeg video and audio decoding plug-in" description="This package contains the FFMpeg plug-in, which is used for decoding video and audio files with OpenAL.')
+        if PkgSkip("VORBIS") and PkgSkip("OPUS"):
+            dist.write('  It is not required for loading .wav files, which Panda3D can read out of the box.">\n')
+        elif PkgSkip("VORBIS"):
+            dist.write('  It is not required for loading .wav or .opus files, which Panda3D can read out of the box.">\n')
+        elif PkgSkip("OPUS"):
+            dist.write('  It is not required for loading .wav or .ogg files, which Panda3D can read out of the box.">\n')
+        else:
+            dist.write('  It is not required for loading .wav, .ogg or .opus files, which Panda3D can read out of the box.">\n')
+        dist.write('        <pkg-ref id="org.panda3d.panda3d.ffmpeg.pkg"/>\n')
+        dist.write('    </choice>\n')
+
+    #if not PkgSkip("OPENAL"):
+    #    dist.write('    <choice id="openal" title="OpenAL Audio Plug-In" tooltip="OpenAL audio output plug-in" description="This package contains the OpenAL audio plug-in, which is an open-source library for playing sounds.">\n')
+    #    dist.write('        <pkg-ref id="org.panda3d.panda3d.openal.pkg"/>\n')
+    #    dist.write('    </choice>\n')
+
+    if not PkgSkip("FMODEX"):
+        dist.write('    <choice id="fmodex" title="FMOD Ex Plug-In" tooltip="FMOD Ex audio output plug-in" description="This package contains the FMOD Ex audio plug-in, which is a commercial library for playing sounds.  It is an optional component as Panda3D can use the open-source alternative OpenAL instead.">\n')
+        dist.write('        <pkg-ref id="org.panda3d.panda3d.fmodex.pkg"/>\n')
+        dist.write('    </choice>\n')
+
+    if os.path.isdir("samples"):
+        dist.write('    <choice id="samples" title="Sample Programs" tooltip="Python sample programs that use Panda3D" description="This package contains the Python sample programs that can help you with learning how to use Panda3D.&#10;&#10;Location: /Developer/Examples/Panda3D/">\n')
+        dist.write('        <pkg-ref id="org.panda3d.panda3d.samples.pkg"/>\n')
+        dist.write('    </choice>\n')
+
+    dist.write('    <choice id="headers" title="C++ Header Files" tooltip="Header files for C++ development with Panda3D" description="This package contains the C++ header files that are needed in order to do C++ development with Panda3D. You don\'t need this if you want to develop in Python.&#10;&#10;Location: /Developer/Panda3D/include/" start_selected="false">\n')
+    dist.write('        <pkg-ref id="org.panda3d.panda3d.headers.pkg"/>\n')
+    dist.write('    </choice>\n')
+    for pkg in pkgs:
+        size = GetDirectorySize("dstroot/" + pkg) // 1024
+        dist.write('    <pkg-ref id="org.panda3d.panda3d.%s.pkg" installKBytes="%d" version="1" auth="Root">file:./Contents/Packages/%s.pkg</pkg-ref>\n' % (pkg, size, pkg))
+    dist.write('</installer-script>\n')
+    dist.close()
+
+    oscmd('hdiutil create Panda3D-rw.dmg -volname "Panda3D SDK %s" -srcfolder dstroot/Panda3D' % (version))
+    oscmd('hdiutil convert Panda3D-rw.dmg -format UDBZ -o %s' % (dmg_name))
+    oscmd('rm -f Panda3D-rw.dmg')
+
+
+def MakeInstallerFreeBSD(version, runtime=False, **kwargs):
+    outputdir = GetOutputDir()
+
+    oscmd("rm -rf targetroot +DESC pkg-plist +MANIFEST")
+    oscmd("mkdir targetroot")
+
+    # Invoke installpanda.py to install it into a temporary dir
+    if runtime:
+        InstallRuntime(destdir="targetroot", prefix="/usr/local", outputdir=outputdir)
+    else:
+        InstallPanda(destdir="targetroot", prefix="/usr/local", outputdir=outputdir)
+
+    if not os.path.exists("/usr/sbin/pkg"):
+        exit("Cannot create an installer without pkg")
+
+    plist_txt = ''
+    for root, dirs, files in os.walk("targetroot/usr/local/", True):
+        for f in files:
+            plist_txt += os.path.join(root, f)[21:] + "\n"
+
+    if not runtime:
+        plist_txt += "@postexec /sbin/ldconfig -m /usr/local/lib/panda3d\n"
+        plist_txt += "@postunexec /sbin/ldconfig -R\n"
+
+        for remdir in ("lib/panda3d", "share/panda3d", "include/panda3d"):
+            for root, dirs, files in os.walk("targetroot/usr/local/" + remdir, False):
+                for d in dirs:
+                    plist_txt += "@dir %s\n" % os.path.join(root, d)[21:]
+            plist_txt += "@dir %s\n" % remdir
+
+    oscmd("echo \"`pkg config abi | tr '[:upper:]' '[:lower:]' | cut -d: -f1,2`:*\" > " + outputdir + "/tmp/architecture.txt")
+    pkg_arch = ReadFile(outputdir+"/tmp/architecture.txt").strip()
+
+    dependencies = ''
+    if not PkgSkip("PYTHON"):
+        # If this version of Python was installed from a package or ports, let's mark it as dependency.
+        oscmd("rm -f %s/tmp/python_dep" % outputdir)
+
+        if "PYTHONVERSION" in SDK:
+            pyver_nodot = SDK["PYTHONVERSION"][6:9:2]
+        else:
+            pyver_nodot = "%d%d" % (sys.version_info[:2])
+
+        oscmd("pkg query \"\n\t%%n : {\n\t\torigin : %%o,\n\t\tversion : %%v\n\t},\n\" python%s > %s/tmp/python_dep" % (pyver_nodot, outputdir), True)
+        if os.path.isfile(outputdir + "/tmp/python_dep"):
+            python_pkg = ReadFile(outputdir + "/tmp/python_dep")
+            if python_pkg:
+                dependencies += python_pkg
+
+    manifest_txt = INSTALLER_PKG_MANIFEST_FILE[1:].replace("NAME", 'panda3d' if not runtime else 'panda3d-runtime')
+    manifest_txt = manifest_txt.replace("VERSION", version)
+    manifest_txt = manifest_txt.replace("ARCH", pkg_arch)
+    manifest_txt = manifest_txt.replace("ORIGIN", 'devel/panda3d' if not runtime else 'graphics/panda3d-runtime')
+    manifest_txt = manifest_txt.replace("DEPENDS", dependencies)
+    manifest_txt = manifest_txt.replace("INSTSIZE", str(GetDirectorySize("targetroot") // 1024 // 1024))
+
+    WriteFile("pkg-plist", plist_txt)
+    WriteFile("+DESC", INSTALLER_PKG_DESCR_FILE[1:] if not runtime else RUNTIME_INSTALLER_PKG_DESCR_FILE[1:])
+    WriteFile("+MANIFEST", manifest_txt)
+    oscmd("pkg create -p pkg-plist -r %s  -m . -o . %s" % (os.path.abspath("targetroot"), "--verbose" if GetVerbose() else "--quiet"))
+
+
+def MakeInstallerAndroid(version, **kwargs):
+    outputdir = GetOutputDir()
+    oscmd("rm -rf apkroot")
+    oscmd("mkdir apkroot")
+
+    # Also remove the temporary apks.
+    apk_unaligned = os.path.join(outputdir, "tmp", "panda3d-unaligned.apk")
+    apk_unsigned = os.path.join(outputdir, "tmp", "panda3d-unsigned.apk")
+    if os.path.exists(apk_unaligned):
+        os.unlink(apk_unaligned)
+    if os.path.exists(apk_unsigned):
+        os.unlink(apk_unsigned)
+
+    # Compile the Java classes into a Dalvik executable.
+    dx_cmd = "dx --dex --output=apkroot/classes.dex "
+    if GetOptimize() <= 2:
+        dx_cmd += "--debug "
+    if GetVerbose():
+        dx_cmd += "--verbose "
+    if "ANDROID_API" in SDK:
+        dx_cmd += "--min-sdk-version=%d " % (SDK["ANDROID_API"])
+    dx_cmd += os.path.join(outputdir, "classes")
+    oscmd(dx_cmd)
+
+    # Copy the libraries one by one.  In case of library dependencies, strip
+    # off any suffix (eg. libfile.so.1.0), as Android does not support them.
+    source_dir = os.path.join(outputdir, "lib")
+    target_dir = os.path.join("apkroot", "lib", SDK["ANDROID_ABI"])
+    oscmd("mkdir -p %s" % (target_dir))
+
+    # Determine the library directories we should look in.
+    libpath = [source_dir]
+    for dir in os.environ.get("LD_LIBRARY_PATH", "").split(':'):
+        dir = os.path.expandvars(dir)
+        dir = os.path.expanduser(dir)
+        if os.path.isdir(dir):
+            dir = os.path.realpath(dir)
+            if not dir.startswith("/system") and not dir.startswith("/vendor"):
+                libpath.append(dir)
+
+    def copy_library(source, base):
+        # Copy file to destination, stripping version suffix.
+        target = os.path.join(target_dir, base)
+        if not target.endswith('.so'):
+            target = target.rpartition('.so.')[0] + '.so'
+
+        if os.path.isfile(target):
+            # Already processed.
+            return
+
+        oscmd("cp %s %s" % (source, target))
+
+        # Walk through the library dependencies.
+        oscmd("ldd %s | grep .so > %s/tmp/otool-libs.txt" % (target, outputdir), True)
+        for line in open(outputdir + "/tmp/otool-libs.txt", "r"):
+            line = line.strip()
+            if not line:
+                continue
+            if '.so.' in line:
+                dep = line.rpartition('.so.')[0] + '.so'
+                oscmd("patchelf --replace-needed %s %s %s" % (line, dep, target), True)
+            else:
+                dep = line
+
+            # Find it on the LD_LIBRARY_PATH.
+            for dir in libpath:
+                fulldep = os.path.join(dir, dep)
+                if os.path.isfile(fulldep):
+                    copy_library(os.path.realpath(fulldep), dep)
+                    break
+
+    # Now copy every lib in the lib dir, and its dependencies.
+    for base in os.listdir(source_dir):
+        if not base.startswith('lib'):
+            continue
+        if not base.endswith('.so') and '.so.' not in base:
+            continue
+
+        source = os.path.join(source_dir, base)
+        if os.path.islink(source):
+            continue
+        copy_library(source, base)
+
+    # Same for Python extension modules.  However, Android is strict about
+    # library naming, so we have a special naming scheme for these, in
+    # conjunction with a custom import hook to find these modules.
+    if not PkgSkip("PYTHON"):
+        suffix = GetExtensionSuffix()
+        source_dir = os.path.join(outputdir, "panda3d")
+        for base in os.listdir(source_dir):
+            if not base.endswith(suffix):
+                continue
+            modname = base[:-len(suffix)]
+            if '.' not in modname:
+                source = os.path.join(source_dir, base)
+                copy_library(source, "libpy.panda3d.{}.so".format(modname))
+
+        # Same for standard Python modules.
+        import _ctypes
+        source_dir = os.path.dirname(_ctypes.__file__)
+        for base in os.listdir(source_dir):
+            if not base.endswith('.so'):
+                continue
+            modname = base.partition('.')[0]
+            source = os.path.join(source_dir, base)
+            copy_library(source, "libpy.{}.so".format(modname))
+
+    def copy_python_tree(source_root, target_root):
+        for source_dir, dirs, files in os.walk(source_root):
+            if 'site-packages' in dirs:
+                dirs.remove('site-packages')
+
+            if not any(base.endswith('.py') for base in files):
+                continue
+
+            target_dir = os.path.join(target_root, os.path.relpath(source_dir, source_root))
+            target_dir = os.path.normpath(target_dir)
+            os.makedirs(target_dir, 0o755)
+
+            for base in files:
+                if base.endswith('.py'):
+                    target = os.path.join(target_dir, base)
+                    shutil.copy(os.path.join(source_dir, base), target)
+
+    # Copy the Python standard library to the .apk as well.
+    from distutils.sysconfig import get_python_lib
+    stdlib_source = get_python_lib(False, True)
+    stdlib_target = os.path.join("apkroot", "lib", "python{0}.{1}".format(*sys.version_info))
+    copy_python_tree(stdlib_source, stdlib_target)
+
+    # But also copy over our custom site.py.
+    shutil.copy("panda/src/android/site.py", os.path.join(stdlib_target, "site.py"))
+
+    # And now make a site-packages directory containing our direct/panda3d/pandac modules.
+    for tree in "panda3d", "direct", "pandac":
+        copy_python_tree(os.path.join(outputdir, tree), os.path.join(stdlib_target, "site-packages", tree))
+
+    # Copy the models and config files to the virtual assets filesystem.
+    oscmd("mkdir apkroot/assets")
+    oscmd("cp -R %s apkroot/assets/models" % (os.path.join(outputdir, "models")))
+    oscmd("cp -R %s apkroot/assets/etc" % (os.path.join(outputdir, "etc")))
+
+    # Make an empty res folder.  It's needed for the apk to be installable, apparently.
+    oscmd("mkdir apkroot/res")
+
+    # Now package up the application
+    oscmd("cp panda/src/android/pview_manifest.xml apkroot/AndroidManifest.xml")
+    aapt_cmd = "aapt package"
+    aapt_cmd += " -F %s" % (apk_unaligned)
+    aapt_cmd += " -M apkroot/AndroidManifest.xml"
+    aapt_cmd += " -A apkroot/assets -S apkroot/res"
+    aapt_cmd += " -I $PREFIX/share/aapt/android.jar"
+    oscmd(aapt_cmd)
+
+    # And add all the libraries to it.
+    oscmd("cd apkroot && aapt add ../%s classes.dex" % (apk_unaligned))
+    for path, dirs, files in os.walk('apkroot/lib'):
+        if files:
+            rel = os.path.relpath(path, 'apkroot')
+            oscmd("cd apkroot && aapt add ../%s %s/*" % (apk_unaligned, rel))
+
+    # Now align the .apk, which is necessary for Android to load it.
+    oscmd("zipalign -v -p 4 %s %s" % (apk_unaligned, apk_unsigned))
+
+    # Finally, sign it using a debug key.  This is generated if it doesn't exist.
+    oscmd("apksigner debug.ks %s panda3d.apk" % (apk_unsigned))
+
+    # Clean up.
+    oscmd("rm -rf apkroot")
+    os.unlink(apk_unaligned)
+    os.unlink(apk_unsigned)
+
+
+def MakeInstaller(version, **kwargs):
+    target = GetTarget()
+    if target == 'windows':
+        fn = "Panda3D-"
+        dir = "Panda3D-" + version
+
+        runtime = kwargs.get('runtime', False)
+        if runtime:
+            fn += "Runtime-"
+            title = "Panda3D " + version
+        else:
+            title = "Panda3D SDK " + version
+
+        fn += version
+
+        python_versions = kwargs.get('python_versions', [])
+        if not runtime and len(python_versions) == 1 and python_versions[0]["version"] != "2.7":
+            fn += '-py' + python_versions[0]["version"]
+
+        if GetOptimize() <= 2:
+            fn += "-dbg"
+        if GetTargetArch() == 'x64':
+            fn += '-x64'
+            dir += '-x64'
+
+        MakeInstallerNSIS(version, fn + '.exe', title, 'C:\\' + dir, **kwargs)
+        if not runtime:
+            MakeDebugSymbolArchive(fn + '-pdb.zip', dir)
+    elif target == 'linux':
+        MakeInstallerLinux(version, **kwargs)
+    elif target == 'darwin':
+        MakeInstallerOSX(version, **kwargs)
+    elif target == 'freebsd':
+        MakeInstallerFreeBSD(version, **kwargs)
+    elif target == 'android':
+        MakeInstallerAndroid(version, **kwargs)
+    else:
+        exit("Do not know how to make an installer for this platform")
+
+
+if __name__ == "__main__":
+    version = ParsePandaVersion("dtool/PandaVersion.pp")
+
+    parser = OptionParser()
+    parser.add_option('', '--version', dest='version', help='Panda3D version number (default: %s)' % (version), default=version)
+    parser.add_option('', '--debversion', dest='debversion', help='Version number for .deb file', default=None)
+    parser.add_option('', '--rpmrelease', dest='rpmrelease', help='Release number for .rpm file', default='1')
+    parser.add_option('', '--outputdir', dest='outputdir', help='Makepanda\'s output directory (default: built)', default='built')
+    parser.add_option('', '--verbose', dest='verbose', help='Enable verbose output', action='store_true', default=False)
+    parser.add_option('', '--runtime', dest='runtime', help='Runtime instead of SDK', action='store_true', default=False)
+    parser.add_option('', '--lzma', dest='compressor', help='Use LZMA compression', action='store_const', const='lzma', default='zlib')
+    (options, args) = parser.parse_args()
+
+    SetVerbose(options.verbose)
+    SetOutputDir(options.outputdir)
+
+    # Read out the optimize option.
+    opt = ReadFile(os.path.join(options.outputdir, "tmp", "optimize.dat"))
+    SetOptimize(int(opt.strip()))
+
+    # Read out whether we should set PkgSkip("PYTHON") and some others.
+    # Temporary hack; needs better solution.
+    pkg_list = "PYTHON", "NVIDIACG", "FFMPEG", "OPENAL", "FMODEX", "PVIEW", "NVIDIACG", "VORBIS", "OPUS"
+    PkgListSet(pkg_list)
+    for pkg in pkg_list:
+        dat_path = "dtool_have_%s.dat" % (pkg.lower())
+        content = ReadFile(os.path.join(options.outputdir, "tmp", dat_path))
+        if int(content.strip()):
+            PkgEnable(pkg)
+        else:
+            PkgDisable(pkg)
+
+    # Parse the version.
+    match = re.match(r'^\d+\.\d+\.\d+', options.version)
+    if not match:
+        exit("version requires three digits")
+
+    MakeInstaller(version=match.group(),
+                  outputdir=options.outputdir,
+                  optimize=GetOptimize(),
+                  compressor=options.compressor,
+                  debversion=options.debversion,
+                  rpmrelease=options.rpmrelease,
+                  runtime=options.runtime,
+                  python_versions=ReadPythonVersionInfoFile())

+ 235 - 961
makepanda/makepanda.py

@@ -23,7 +23,7 @@ except:
     exit(1)
 
 from makepandacore import *
-from installpanda import *
+from distutils.util import get_platform
 import time
 import os
 import sys
@@ -71,6 +71,8 @@ WINDOWS_SDK = None
 MSVC_VERSION = None
 BOOUSEINTELCOMPILER = False
 OPENCV_VER_23 = False
+PLATFORM = None
+COPY_PYTHON = True
 
 if "MACOSX_DEPLOYMENT_TARGET" in os.environ:
     OSXTARGET=os.environ["MACOSX_DEPLOYMENT_TARGET"]
@@ -91,7 +93,7 @@ PkgListSet(["PYTHON", "DIRECT",                        # Python support
   "ARTOOLKIT", "OPENCV", "DIRECTCAM", "VISION",        # Augmented Reality
   "GTK2",                                              # GTK2 is used for PStats on Unix
   "MFC", "WX", "FLTK",                                 # Used for web plug-in only
-  "ROCKET", "AWESOMIUM",                               # GUI libraries
+  "ROCKET",                                            # GUI libraries
   "CARBON", "COCOA",                                   # Mac OS X toolkits
   "X11",                                               # Unix platform support
   "PANDATOOL", "PVIEW", "DEPLOYTOOLS",                 # Toolchain
@@ -171,14 +173,23 @@ def parseopts(args):
     global COMPRESSOR,THREADCOUNT,OSXTARGET,OSX_ARCHS,HOST_URL
     global DEBVERSION,WHLVERSION,RPMRELEASE,GIT_COMMIT,P3DSUFFIX,RTDIST_VERSION
     global STRDXSDKVERSION, WINDOWS_SDK, MSVC_VERSION, BOOUSEINTELCOMPILER
+    global COPY_PYTHON
+
+    # Options for which to display a deprecation warning.
+    removedopts = [
+        "use-touchinput", "no-touchinput", "no-awesomium", "no-directscripts",
+        ]
+
+    # All recognized options.
     longopts = [
         "help","distributor=","verbose","runtime","osxtarget=","tests",
         "optimize=","everything","nothing","installer","wheel","rtdist","nocolor",
         "version=","lzma","no-python","threads=","outputdir=","override=",
         "static","host=","debversion=","rpmrelease=","p3dsuffix=","rtdist-version=",
         "directx-sdk=", "windows-sdk=", "msvc-version=", "clean", "use-icl",
-        "universal", "target=", "arch=", "git-commit=", "no-directscripts",
-        "use-touchinput", "no-touchinput"]
+        "universal", "target=", "arch=", "git-commit=", "no-copy-python",
+        ] + removedopts
+
     anything = 0
     optimize = ""
     target = None
@@ -190,6 +201,7 @@ def parseopts(args):
         longopts.append("no-" + pkg.lower())
         longopts.append(pkg.lower() + "-incdir=")
         longopts.append(pkg.lower() + "-libdir=")
+
     try:
         opts, extras = getopt.getopt(args, "", longopts)
         for option, value in opts:
@@ -230,7 +242,6 @@ def parseopts(args):
             # Backward compatibility, OPENGL was renamed to GL
             elif (option=="--use-opengl"): PkgEnable("GL")
             elif (option=="--no-opengl"): PkgDisable("GL")
-            elif (option=="--no-directscripts"): pass
             elif (option=="--directx-sdk"):
                 STRDXSDKVERSION = value.strip().lower()
                 if STRDXSDKVERSION == '':
@@ -242,6 +253,9 @@ def parseopts(args):
                 MSVC_VERSION = value.strip().lower()
             elif (option=="--use-icl"): BOOUSEINTELCOMPILER = True
             elif (option=="--clean"): clean_build = True
+            elif (option=="--no-copy-python"): COPY_PYTHON = False
+            elif (option[2:] in removedopts):
+                Warn("Ignoring removed option %s" % (option))
             else:
                 for pkg in PkgListGet():
                     if option == "--use-" + pkg.lower():
@@ -403,6 +417,67 @@ MAJOR_VERSION = '.'.join(VERSION.split('.')[:2])
 if P3DSUFFIX is None:
     P3DSUFFIX = MAJOR_VERSION
 
+# Now determine the distutils-style platform tag for the target system.
+target = GetTarget()
+if target == 'windows':
+    if GetTargetArch() == 'x64':
+        PLATFORM = 'win-amd64'
+    else:
+        PLATFORM = 'win32'
+
+elif target == 'darwin':
+    if OSXTARGET:
+        osxver = OSXTARGET
+    else:
+        maj, min = platform.mac_ver()[0].split('.')[:2]
+        osxver = int(maj), int(min)
+
+    arch_tag = None
+    if not OSX_ARCHS:
+        arch_tag = GetTargetArch()
+    elif len(OSX_ARCHS) == 1:
+        arch_tag = OSX_ARCHS[0]
+    elif frozenset(OSX_ARCHS) == frozenset(('i386', 'ppc')):
+        arch_tag = 'fat'
+    elif frozenset(OSX_ARCHS) == frozenset(('x86_64', 'i386')):
+        arch_tag = 'intel'
+    elif frozenset(OSX_ARCHS) == frozenset(('x86_64', 'ppc64')):
+        arch_tag = 'fat64'
+    elif frozenset(OSX_ARCHS) == frozenset(('x86_64', 'i386', 'ppc')):
+        arch_tag = 'fat32'
+    else:
+        raise RuntimeError('No arch tag for arch combination %s' % OSX_ARCHS)
+
+    PLATFORM = 'macosx-{0}.{1}-{2}'.format(osxver[0], osxver[1], arch_tag)
+
+elif target == 'linux' and (os.path.isfile("/lib/libc-2.5.so") or os.path.isfile("/lib64/libc-2.5.so")) and os.path.isdir("/opt/python"):
+    # This is manylinux1.  A bit of a sloppy check, though.
+    if GetTargetArch() in ('x86_64', 'amd64'):
+        PLATFORM = 'manylinux1-x86_64'
+    else:
+        PLATFORM = 'manylinux1-i686'
+
+elif not CrossCompiling():
+    if HasTargetArch():
+        # Replace the architecture in the platform string.
+        platform_parts = get_platform().rsplit('-', 1)
+        target_arch = GetTargetArch()
+        if target_arch == 'amd64':
+            target_arch = 'x86_64'
+        PLATFORM = platform_parts[0] + '-' + target_arch
+    else:
+        # We're not cross-compiling; just take the host arch.
+        PLATFORM = get_platform()
+
+else:
+    target_arch = GetTargetArch()
+    if target_arch == 'amd64':
+        target_arch = 'x86_64'
+    PLATFORM = '{0}-{1}' % (target, target_arch)
+
+
+print("Platform: %s" % PLATFORM)
+
 outputdir_suffix = ""
 
 if (RUNTIME or RTDIST):
@@ -616,6 +691,7 @@ if (COMPILER == "MSVC"):
     LibName("WINGDI", "gdi32.lib")
     LibName("ADVAPI", "advapi32.lib")
     LibName("IPHLPAPI", "iphlpapi.lib")
+    LibName("SETUPAPI", "setupapi.lib")
     LibName("GL", "opengl32.lib")
     LibName("GLES", "libgles_cm.lib")
     LibName("GLES2", "libGLESv2.lib")
@@ -646,10 +722,17 @@ if (COMPILER == "MSVC"):
         suffix = ""
         if os.path.isfile(GetThirdpartyDir() + "openexr/lib/IlmImf-2_2.lib"):
             suffix = "-2_2"
+        elif os.path.isfile(GetThirdpartyDir() + "openexr/lib/IlmImf-2_3.lib"):
+            suffix = "-2_3"
+        if os.path.isfile(GetThirdpartyDir() + "openexr/lib/IlmImf" + suffix + "_s.lib"):
+            suffix += "_s"
         LibName("OPENEXR", GetThirdpartyDir() + "openexr/lib/IlmImf" + suffix + ".lib")
         LibName("OPENEXR", GetThirdpartyDir() + "openexr/lib/IlmThread" + suffix + ".lib")
         LibName("OPENEXR", GetThirdpartyDir() + "openexr/lib/Iex" + suffix + ".lib")
-        LibName("OPENEXR", GetThirdpartyDir() + "openexr/lib/Half.lib")
+        if suffix == "-2_2":
+            LibName("OPENEXR", GetThirdpartyDir() + "openexr/lib/Half.lib")
+        else:
+            LibName("OPENEXR", GetThirdpartyDir() + "openexr/lib/Half" + suffix + ".lib")
         IncDirectory("OPENEXR", GetThirdpartyDir() + "openexr/include/OpenEXR")
     if (PkgSkip("JPEG")==0):     LibName("JPEG",     GetThirdpartyDir() + "jpeg/lib/jpeg-static.lib")
     if (PkgSkip("ZLIB")==0):     LibName("ZLIB",     GetThirdpartyDir() + "zlib/lib/zlibstatic.lib")
@@ -669,7 +752,6 @@ if (COMPILER == "MSVC"):
     if (PkgSkip("OPENCV")==0):   LibName("OPENCV",   GetThirdpartyDir() + "opencv/lib/cvaux.lib")
     if (PkgSkip("OPENCV")==0):   LibName("OPENCV",   GetThirdpartyDir() + "opencv/lib/ml.lib")
     if (PkgSkip("OPENCV")==0):   LibName("OPENCV",   GetThirdpartyDir() + "opencv/lib/cxcore.lib")
-    if (PkgSkip("AWESOMIUM")==0):LibName("AWESOMIUM",GetThirdpartyDir() + "awesomium/lib/Awesomium.lib")
     if (PkgSkip("FFMPEG")==0):   LibName("FFMPEG",   GetThirdpartyDir() + "ffmpeg/lib/avcodec.lib")
     if (PkgSkip("FFMPEG")==0):   LibName("FFMPEG",   GetThirdpartyDir() + "ffmpeg/lib/avformat.lib")
     if (PkgSkip("FFMPEG")==0):   LibName("FFMPEG",   GetThirdpartyDir() + "ffmpeg/lib/avutil.lib")
@@ -680,8 +762,7 @@ if (COMPILER == "MSVC"):
         IncDirectory("FCOLLADA", GetThirdpartyDir() + "fcollada/include/FCollada")
     if (PkgSkip("ASSIMP")==0):
         LibName("ASSIMP", GetThirdpartyDir() + "assimp/lib/assimp.lib")
-        path = GetThirdpartyDir() + "assimp/lib/IrrXML.lib"
-        if os.path.isfile(path):
+        if os.path.isfile(GetThirdpartyDir() + "assimp/lib/IrrXML.lib"):
             LibName("ASSIMP", GetThirdpartyDir() + "assimp/lib/IrrXML.lib")
         IncDirectory("ASSIMP", GetThirdpartyDir() + "assimp/include")
     if (PkgSkip("SQUISH")==0):
@@ -790,7 +871,6 @@ if (COMPILER == "MSVC"):
         LibName("BULLET", GetThirdpartyDir() + "bullet/lib/BulletSoftBody" + suffix)
 
 if (COMPILER=="GCC"):
-    PkgDisable("AWESOMIUM")
     if GetTarget() != "darwin":
         PkgDisable("CARBON")
         PkgDisable("COCOA")
@@ -887,11 +967,12 @@ if (COMPILER=="GCC"):
 
         if not PkgSkip("PYTHON"):
             python_lib = SDK["PYTHONVERSION"]
-            if not RTDIST and GetTarget() != 'android':
-                # We don't link anything in the SDK with libpython.
-                python_lib = ""
             SmartPkgEnable("PYTHON", "", python_lib, (SDK["PYTHONVERSION"], SDK["PYTHONVERSION"] + "/Python.h"))
 
+            if GetTarget() == "linux":
+                LibName("PYTHON", "-lutil")
+                LibName("PYTHON", "-lrt")
+
     SmartPkgEnable("OPENSSL",   "openssl",   ("ssl", "crypto"), ("openssl/ssl.h", "openssl/crypto.h"))
     SmartPkgEnable("ZLIB",      "zlib",      ("z"), "zlib.h")
     SmartPkgEnable("GTK2",      "gtk+-2.0")
@@ -1983,7 +2064,7 @@ def FreezePy(target, inputs, opts):
     if sys.version_info >= (2, 6):
         cmdstr += "-B "
 
-    cmdstr += os.path.join(GetOutputDir(), "direct", "showutil", "pfreeze.py")
+    cmdstr += os.path.join(GetOutputDir(), "direct", "dist", "pfreeze.py")
 
     if 'FREEZE_STARTUP' in opts:
         cmdstr += " -s"
@@ -2440,6 +2521,7 @@ def WriteConfigSettings():
         dtool_config["HAVE_GLX"] = 'UNDEF'
         dtool_config["IS_LINUX"] = 'UNDEF'
         dtool_config["HAVE_VIDEO4LINUX"] = 'UNDEF'
+        dtool_config["PHAVE_LINUX_INPUT_H"] = 'UNDEF'
         dtool_config["IS_OSX"] = '1'
         # 10.4 had a broken ucontext implementation
         if int(platform.mac_ver()[0][3]) <= 4:
@@ -2576,6 +2658,14 @@ def WriteConfigSettings():
         if (PkgSkip(x)): ConditionalWriteFile(GetOutputDir() + '/tmp/dtool_have_'+x.lower()+'.dat', "0\n")
         else:            ConditionalWriteFile(GetOutputDir() + '/tmp/dtool_have_'+x.lower()+'.dat', "1\n")
 
+    # Finally, write a platform.dat with the platform we are compiling for.
+    ConditionalWriteFile(GetOutputDir() + '/tmp/platform.dat', PLATFORM)
+
+    # This is useful for tools like makepackage that need to know things about
+    # the build parameters.
+    ConditionalWriteFile(GetOutputDir() + '/tmp/optimize.dat', str(GetOptimize()))
+
+
 WriteConfigSettings()
 
 WarnConflictingFiles()
@@ -2802,16 +2892,16 @@ __version__ = '%s'
 
 if GetTarget() == 'windows':
     p3d_init += """
-import os
+if '__file__' in locals():
+    import os
 
-bindir = os.path.join(os.path.dirname(__file__), '..', 'bin')
-if os.path.isdir(bindir):
-    if not os.environ.get('PATH'):
-        os.environ['PATH'] = bindir
-    else:
-        os.environ['PATH'] = bindir + os.pathsep + os.environ['PATH']
-
-del os, bindir
+    bindir = os.path.join(os.path.dirname(__file__), '..', 'bin')
+    if os.path.isdir(bindir):
+        if not os.environ.get('PATH'):
+            os.environ['PATH'] = bindir
+        else:
+            os.environ['PATH'] = bindir + os.pathsep + os.environ['PATH']
+    del os, bindir
 """
 
 if not PkgSkip("PYTHON"):
@@ -2842,8 +2932,6 @@ if not PkgSkip("SKEL"):
     panda_modules.append('skel')
 if not PkgSkip("EGG"):
     panda_modules.append('egg')
-if not PkgSkip("AWESOMIUM"):
-    panda_modules.append('awesomium')
 if not PkgSkip("ODE"):
     panda_modules.append('ode')
 if not PkgSkip("VRPN"):
@@ -2888,6 +2976,36 @@ if not PkgSkip("PYTHON"):
     ConditionalWriteFile(GetOutputDir() + '/pandac/extension_native_helpers.py', exthelpers_code)
     ConditionalWriteFile(GetOutputDir() + '/pandac/__init__.py', '')
 
+##########################################################################################
+#
+# Write the dist-info directory.
+#
+##########################################################################################
+
+# This is just some basic stuff since setuptools just needs this file to
+# exist, otherwise it will not read the entry_points.txt file.  Maybe we will
+# eventually want to merge this with the metadata generator in makewheel.py.
+METADATA = """Metadata-Version: 2.0
+Name: Panda3D
+Version: {version}
+License: BSD
+Home-page: https://www.panda3d.org/
+Author: Panda3D Team
+Author-email: [email protected]
+"""
+
+ENTRY_POINTS = """[distutils.commands]
+build_apps = direct.dist.commands:build_apps
+bdist_apps = direct.dist.commands:bdist_apps
+"""
+
+if not PkgSkip("DIRECT"):
+    dist_dir = os.path.join(GetOutputDir(), 'panda3d.dist-info')
+    MakeDirectory(dist_dir)
+
+    ConditionalWriteFile(os.path.join(dist_dir, 'METADATA'), METADATA.format(version=VERSION))
+    ConditionalWriteFile(os.path.join(dist_dir, 'entry_points.txt'), ENTRY_POINTS)
+
 ##########################################################################################
 #
 # Generate the PRC files into the ETC directory.
@@ -2916,7 +3034,7 @@ else:
     configprc = configprc.replace("aux-display pandadx9", "")
 
 if (GetTarget() == 'darwin'):
-    configprc = configprc.replace("$XDG_CACHE_HOME/panda3d", "Library/Caches/Panda3D-%s" % MAJOR_VERSION)
+    configprc = configprc.replace("$XDG_CACHE_HOME/panda3d", "$HOME/Library/Caches/Panda3D-%s" % MAJOR_VERSION)
 
     # OpenAL is not yet working well on OSX for us, so let's do this for now.
     configprc = configprc.replace("p3openal_audio", "p3fmod_audio")
@@ -3040,7 +3158,8 @@ if tp_dir is not None:
                 CopyFile(GetOutputDir() + "/bin/", fn)
 
             # Copy the whole Python directory.
-            CopyTree(GetOutputDir() + "/python", SDK["PYTHON"])
+            if COPY_PYTHON:
+                CopyTree(GetOutputDir() + "/python", SDK["PYTHON"])
 
             # NB: Python does not always ship with the correct manifest/dll.
             # Figure out the correct one to ship, and grab it from WinSxS dir.
@@ -3049,7 +3168,7 @@ if tp_dir is not None:
                 os.unlink(manifest)
             oscmd('mt -inputresource:"%s\\python.exe";#1 -out:"%s" -nologo' % (SDK["PYTHON"], manifest), True)
 
-            if os.path.isfile(manifest):
+            if COPY_PYTHON and os.path.isfile(manifest):
                 import xml.etree.ElementTree as ET
                 tree = ET.parse(manifest)
                 idents = tree.findall('./{urn:schemas-microsoft-com:asm.v1}dependency/{urn:schemas-microsoft-com:asm.v1}dependentAssembly/{urn:schemas-microsoft-com:asm.v1}assemblyIdentity')
@@ -3079,11 +3198,12 @@ if tp_dir is not None:
                     CopyFile(GetOutputDir() + "/python/", file)
 
             # Copy python.exe to ppython.exe.
-            if not os.path.isfile(SDK["PYTHON"] + "/ppython.exe") and os.path.isfile(SDK["PYTHON"] + "/python.exe"):
-                CopyFile(GetOutputDir() + "/python/ppython.exe", SDK["PYTHON"] + "/python.exe")
-            if not os.path.isfile(SDK["PYTHON"] + "/ppythonw.exe") and os.path.isfile(SDK["PYTHON"] + "/pythonw.exe"):
-                CopyFile(GetOutputDir() + "/python/ppythonw.exe", SDK["PYTHON"] + "/pythonw.exe")
-            ConditionalWriteFile(GetOutputDir() + "/python/panda.pth", "..\n../bin\n")
+            if COPY_PYTHON:
+                if not os.path.isfile(SDK["PYTHON"] + "/ppython.exe") and os.path.isfile(SDK["PYTHON"] + "/python.exe"):
+                    CopyFile(GetOutputDir() + "/python/ppython.exe", SDK["PYTHON"] + "/python.exe")
+                if not os.path.isfile(SDK["PYTHON"] + "/ppythonw.exe") and os.path.isfile(SDK["PYTHON"] + "/pythonw.exe"):
+                    CopyFile(GetOutputDir() + "/python/ppythonw.exe", SDK["PYTHON"] + "/pythonw.exe")
+                ConditionalWriteFile(GetOutputDir() + "/python/panda.pth", "..\n../bin\n")
 
 # Copy over the MSVC runtime.
 if GetTarget() == 'windows' and "VISUALSTUDIO" in SDK:
@@ -3189,7 +3309,6 @@ CopyAllHeaders('panda/src/text')
 CopyAllHeaders('panda/src/grutil')
 if (PkgSkip("VISION")==0):
     CopyAllHeaders('panda/src/vision')
-CopyAllHeaders('panda/src/awesomium')
 if (PkgSkip("FFMPEG")==0):
     CopyAllHeaders('panda/src/ffmpeg')
 CopyAllHeaders('panda/src/tform')
@@ -3867,6 +3986,34 @@ if (not RUNTIME):
   TargetAdd('libp3cull.in', opts=OPTS, input=IGATEFILES)
   TargetAdd('libp3cull.in', opts=['IMOD:panda3d.core', 'ILIB:libp3cull', 'SRCDIR:panda/src/cull'])
 
+#
+# DIRECTORY: panda/src/dgraph/
+#
+
+if (not RUNTIME):
+  OPTS=['DIR:panda/src/dgraph', 'BUILDING:PANDA']
+  TargetAdd('p3dgraph_composite1.obj', opts=OPTS, input='p3dgraph_composite1.cxx')
+  TargetAdd('p3dgraph_composite2.obj', opts=OPTS, input='p3dgraph_composite2.cxx')
+
+  OPTS=['DIR:panda/src/dgraph']
+  IGATEFILES=GetDirectoryContents('panda/src/dgraph', ["*.h", "*_composite*.cxx"])
+  TargetAdd('libp3dgraph.in', opts=OPTS, input=IGATEFILES)
+  TargetAdd('libp3dgraph.in', opts=['IMOD:panda3d.core', 'ILIB:libp3dgraph', 'SRCDIR:panda/src/dgraph'])
+
+#
+# DIRECTORY: panda/src/device/
+#
+
+if (not RUNTIME):
+  OPTS=['DIR:panda/src/device', 'BUILDING:PANDA']
+  TargetAdd('p3device_composite1.obj', opts=OPTS, input='p3device_composite1.cxx')
+  TargetAdd('p3device_composite2.obj', opts=OPTS, input='p3device_composite2.cxx')
+
+  OPTS=['DIR:panda/src/device']
+  IGATEFILES=GetDirectoryContents('panda/src/device', ["*.h", "*_composite*.cxx"])
+  TargetAdd('libp3device.in', opts=OPTS, input=IGATEFILES)
+  TargetAdd('libp3device.in', opts=['IMOD:panda3d.core', 'ILIB:libp3device', 'SRCDIR:panda/src/device'])
+
 #
 # DIRECTORY: panda/src/display/
 #
@@ -3919,34 +4066,6 @@ if (not RUNTIME):
   TargetAdd('libp3char.in', opts=OPTS, input=IGATEFILES)
   TargetAdd('libp3char.in', opts=['IMOD:panda3d.core', 'ILIB:libp3char', 'SRCDIR:panda/src/char'])
 
-#
-# DIRECTORY: panda/src/dgraph/
-#
-
-if (not RUNTIME):
-  OPTS=['DIR:panda/src/dgraph', 'BUILDING:PANDA']
-  TargetAdd('p3dgraph_composite1.obj', opts=OPTS, input='p3dgraph_composite1.cxx')
-  TargetAdd('p3dgraph_composite2.obj', opts=OPTS, input='p3dgraph_composite2.cxx')
-
-  OPTS=['DIR:panda/src/dgraph']
-  IGATEFILES=GetDirectoryContents('panda/src/dgraph', ["*.h", "*_composite*.cxx"])
-  TargetAdd('libp3dgraph.in', opts=OPTS, input=IGATEFILES)
-  TargetAdd('libp3dgraph.in', opts=['IMOD:panda3d.core', 'ILIB:libp3dgraph', 'SRCDIR:panda/src/dgraph'])
-
-#
-# DIRECTORY: panda/src/device/
-#
-
-if (not RUNTIME):
-  OPTS=['DIR:panda/src/device', 'BUILDING:PANDA']
-  TargetAdd('p3device_composite1.obj', opts=OPTS, input='p3device_composite1.cxx')
-  TargetAdd('p3device_composite2.obj', opts=OPTS, input='p3device_composite2.cxx')
-
-  OPTS=['DIR:panda/src/device']
-  IGATEFILES=GetDirectoryContents('panda/src/device', ["*.h", "*_composite*.cxx"])
-  TargetAdd('libp3device.in', opts=OPTS, input=IGATEFILES)
-  TargetAdd('libp3device.in', opts=['IMOD:panda3d.core', 'ILIB:libp3device', 'SRCDIR:panda/src/device'])
-
 #
 # DIRECTORY: panda/src/pnmtext/
 #
@@ -4111,7 +4230,8 @@ if (not RUNTIME):
 if (not RUNTIME):
   OPTS=['DIR:panda/metalibs/panda', 'BUILDING:PANDA', 'JPEG', 'PNG', 'HARFBUZZ',
       'TIFF', 'OPENEXR', 'ZLIB', 'OPENSSL', 'FREETYPE', 'FFTW', 'ADVAPI', 'WINSOCK2',
-      'SQUISH', 'NVIDIACG', 'VORBIS', 'OPUS', 'WINUSER', 'WINMM', 'WINGDI', 'IPHLPAPI']
+      'SQUISH', 'NVIDIACG', 'VORBIS', 'OPUS', 'WINUSER', 'WINMM', 'WINGDI', 'IPHLPAPI',
+      'SETUPAPI']
 
   TargetAdd('panda_panda.obj', opts=OPTS, input='panda.cxx')
 
@@ -4363,32 +4483,6 @@ if (PkgSkip("ROCKET") == 0) and (not RUNTIME):
   PyTargetAdd('rocket.pyd', input=COMMON_PANDA_LIBS)
   PyTargetAdd('rocket.pyd', opts=['ROCKET'])
 
-#
-# DIRECTORY: panda/src/p3awesomium
-#
-if PkgSkip("AWESOMIUM") == 0 and not RUNTIME:
-  OPTS=['DIR:panda/src/awesomium', 'BUILDING:PANDAAWESOMIUM',  'AWESOMIUM']
-  TargetAdd('pandaawesomium_composite1.obj', opts=OPTS, input='pandaawesomium_composite1.cxx')
-  TargetAdd('libp3awesomium.dll', input='pandaawesomium_composite1.obj')
-  TargetAdd('libp3awesomium.dll', input=COMMON_PANDA_LIBS)
-  TargetAdd('libp3awesomium.dll', opts=OPTS)
-
-  OPTS=['DIR:panda/src/awesomium', 'AWESOMIUM']
-  IGATEFILES=GetDirectoryContents('panda/src/awesomium', ["*.h", "*_composite1.cxx"])
-  TargetAdd('libp3awesomium.in', opts=OPTS, input=IGATEFILES)
-  TargetAdd('libp3awesomium.in', opts=['IMOD:panda3d.awesomium', 'ILIB:libp3awesomium', 'SRCDIR:panda/src/awesomium'])
-
-
-  PyTargetAdd('awesomium_module.obj', input='libp3awesomium.in')
-  PyTargetAdd('awesomium_module.obj', opts=OPTS)
-  PyTargetAdd('awesomium_module.obj', opts=['IMOD:panda3d.awesomium', 'ILIB:awesomium', 'IMPORT:panda3d.core'])
-
-  PyTargetAdd('awesomium.pyd', input='awesomium_module.obj')
-  PyTargetAdd('awesomium.pyd', input='libp3awesomium_igate.obj')
-  PyTargetAdd('awesomium.pyd', input='libp3awesomium.dll')
-  PyTargetAdd('awesomium.pyd', input='libp3interrogatedb.dll')
-  PyTargetAdd('awesomium.pyd', input=COMMON_PANDA_LIBS)
-
 #
 # DIRECTORY: panda/src/p3skel
 #
@@ -6537,6 +6631,36 @@ if not PkgSkip("CONTRIB") and not PkgSkip("PYTHON") and not RUNTIME:
   PyTargetAdd('_rplight.pyd', input='libp3interrogatedb.dll')
   PyTargetAdd('_rplight.pyd', input=COMMON_PANDA_LIBS)
 
+#
+# DIRECTORY: pandatool/src/deploy-stub
+#
+if PkgSkip("PYTHON") == 0:
+    OPTS=['DIR:pandatool/src/deploy-stub', 'BUILDING:DEPLOYSTUB']
+    PyTargetAdd('deploy-stub.obj', opts=OPTS, input='deploy-stub.c')
+    if GetTarget() == 'windows':
+        PyTargetAdd('frozen_dllmain.obj', opts=OPTS, input='frozen_dllmain.c')
+
+    if GetTarget() == 'linux' or GetTarget() == 'freebsd':
+        # Setup rpath so libs can be found in the same directory as the deployed game
+        LibName('DEPLOYSTUB', "-Wl,-rpath,\$ORIGIN")
+        LibName('DEPLOYSTUB', "-Wl,-z,origin")
+        LibName('DEPLOYSTUB', "-rdynamic")
+    PyTargetAdd('deploy-stub.exe', input='deploy-stub.obj')
+    if GetTarget() == 'windows':
+        PyTargetAdd('deploy-stub.exe', input='frozen_dllmain.obj')
+    PyTargetAdd('deploy-stub.exe', opts=['WINSHELL', 'DEPLOYSTUB', 'NOICON'])
+
+    if GetTarget() == 'windows':
+        PyTargetAdd('deploy-stubw.exe', input='deploy-stub.obj')
+        PyTargetAdd('deploy-stubw.exe', input='frozen_dllmain.obj')
+        PyTargetAdd('deploy-stubw.exe', opts=['SUBSYSTEM:WINDOWS', 'WINSHELL', 'DEPLOYSTUB', 'NOICON'])
+    elif GetTarget() == 'darwin':
+        DefSymbol('MACOS_APP_BUNDLE', 'MACOS_APP_BUNDLE')
+        OPTS = OPTS + ['MACOS_APP_BUNDLE']
+        PyTargetAdd('deploy-stubw.obj', opts=OPTS, input='deploy-stub.c')
+        PyTargetAdd('deploy-stubw.exe', input='deploy-stubw.obj')
+        PyTargetAdd('deploy-stubw.exe', opts=['MACOS_APP_BUNDLE', 'DEPLOYSTUB', 'NOICON'])
+
 #
 # Generate the models directory and samples directory
 #
@@ -6727,17 +6851,18 @@ def SequentialMake(tasklist):
             JustBuilt(task[2], task[3])
         i += 1
 
+
 def RunDependencyQueue(tasklist):
     if (THREADCOUNT!=0):
         ParallelMake(tasklist)
     else:
         SequentialMake(tasklist)
 
+
 try:
     RunDependencyQueue(DEPENDENCYQUEUE)
-except:
+finally:
     SaveDependencyCache()
-    raise
 
 # Run the test suite.
 if RUNTESTS:
@@ -6749,6 +6874,10 @@ if RUNTESTS:
         cmdstr += " --verbose"
     oscmd(cmdstr)
 
+# Write out information about the Python versions in the built dir.
+python_version_info = GetCurrentPythonVersionInfo()
+UpdatePythonVersionInfoFile(python_version_info)
+
 ##########################################################################################
 #
 # The Installers
@@ -6759,879 +6888,24 @@ if RUNTESTS:
 #
 ##########################################################################################
 
-def MakeInstallerNSIS(file, title, installdir):
-    if (os.path.isfile(file)):
-        os.remove(file)
-    elif (os.path.isdir(file)):
-        shutil.rmtree(file)
-
-    pyver = SDK["PYTHONVERSION"][6:9]
-    if GetTargetArch() == 'x64':
-        regview = '64'
-    else:
-        regview = '32'
-        if int(pyver[0]) == 3 and int(pyver[2]) >= 5:
-            pyver += '-32'
-
-    if (RUNTIME):
-        # Invoke the make_installer script.
-        AddToPathEnv("PATH", GetOutputDir() + "\\bin")
-        AddToPathEnv("PATH", GetOutputDir() + "\\plugins")
-
-        cmd = sys.executable + " -B -u " + os.path.join("direct", "src", "plugin_installer", "make_installer.py")
-        cmd += " --version %s --regview %s" % (VERSION, regview)
-
-        if GetTargetArch() == 'x64':
-            cmd += " --install \"$PROGRAMFILES64\\Panda3D\" "
-        else:
-            cmd += " --install \"$PROGRAMFILES32\\Panda3D\" "
-
-        oscmd(cmd)
-        shutil.move(os.path.join("direct", "src", "plugin_installer", "p3d-setup.exe"), file)
-        return
-
-    print("Building "+title+" installer at %s" % (file))
-    if (COMPRESSOR != "lzma"):
-        print("Note: you are using zlib, which is faster, but lzma gives better compression.")
-    if (os.path.exists("nsis-output.exe")):
-        os.remove("nsis-output.exe")
-    WriteFile(GetOutputDir()+"/tmp/__init__.py", "")
-
-    nsis_defs = {
-        'COMPRESSOR'  : COMPRESSOR,
-        'TITLE'       : title,
-        'INSTALLDIR'  : installdir,
-        'OUTFILE'     : '..\\' + file,
-        'BUILT'       : '..\\' + GetOutputDir(),
-        'SOURCE'      : '..',
-        'PYVER'       : pyver,
-        'REGVIEW'     : regview,
-        'EXT_SUFFIX'  : GetExtensionSuffix(),
-    }
-
-    if GetHost() == 'windows':
-        cmd = os.path.join(GetThirdpartyBase(), 'win-nsis', 'makensis') + ' /V2'
-        for item in nsis_defs.items():
-            cmd += ' /D%s="%s"' % item
-    else:
-        cmd = 'makensis -V2'
-        for item in nsis_defs.items():
-            cmd += ' -D%s="%s"' % item
-
-    cmd += ' "makepanda\\installer.nsi"'
-    oscmd(cmd)
-
-def MakeDebugSymbolArchive(zipname, dirname):
-    import zipfile
-    zip = zipfile.ZipFile(zipname, 'w', zipfile.ZIP_DEFLATED)
-
-    for fn in glob.glob(os.path.join(GetOutputDir(), 'bin', '*.pdb')):
-        zip.write(fn, dirname + '/bin/' + os.path.basename(fn))
-
-    for fn in glob.glob(os.path.join(GetOutputDir(), 'panda3d', '*.pdb')):
-        zip.write(fn, dirname + '/panda3d/' + os.path.basename(fn))
-
-    for fn in glob.glob(os.path.join(GetOutputDir(), 'plugins', '*.pdb')):
-        zip.write(fn, dirname + '/plugins/' + os.path.basename(fn))
-
-    for fn in glob.glob(os.path.join(GetOutputDir(), 'python', '*.pdb')):
-        zip.write(fn, dirname + '/python/' + os.path.basename(fn))
-
-    for fn in glob.glob(os.path.join(GetOutputDir(), 'python', 'DLLs', '*.pdb')):
-        zip.write(fn, dirname + '/python/DLLs/' + os.path.basename(fn))
-
-    zip.close()
-
-INSTALLER_DEB_FILE="""
-Package: panda3dMAJOR
-Version: VERSION
-Section: libdevel
-Priority: optional
-Architecture: ARCH
-Essential: no
-Depends: DEPENDS
-Recommends: RECOMMENDS
-Provides: panda3d, pythonPV-panda3d
-Conflicts: panda3d, pythonPV-panda3d
-Replaces: panda3d, pythonPV-panda3d
-Maintainer: rdb <[email protected]>
-Installed-Size: INSTSIZE
-Description: Panda3D free 3D engine SDK
- Panda3D is a game engine which includes graphics, audio, I/O, collision detection, and other abilities relevant to the creation of 3D games. Panda3D is open source and free software under the revised BSD license, and can be used for both free and commercial game development at no financial cost.
- Panda3D's intended game-development language is Python. The engine itself is written in C++, and utilizes an automatic wrapper-generator to expose the complete functionality of the engine in a Python interface.
- .
- This package contains the SDK for development with Panda3D.
-
-"""
-
-RUNTIME_INSTALLER_DEB_FILE="""
-Package: panda3d-runtime
-Version: VERSION
-Section: web
-Priority: optional
-Architecture: ARCH
-Essential: no
-Depends: DEPENDS
-Provides: panda3d-runtime
-Maintainer: rdb <[email protected]>
-Installed-Size: INSTSIZE
-Description: Runtime binary and browser plugin for the Panda3D Game Engine
- This package contains the runtime distribution and browser plugin of the Panda3D engine. It allows you view webpages that contain Panda3D content and to run games created with Panda3D that are packaged as .p3d file.
-
-"""
-
-
-# We're not putting "python" in the "Requires" field,
-# since the rpm-based distros don't have a common
-# naming for the Python package.
-INSTALLER_SPEC_FILE="""
-Summary: The Panda3D free 3D engine SDK
-Name: panda3d
-Version: VERSION
-Release: RPMRELEASE
-License: BSD License
-Group: Development/Libraries
-BuildRoot: PANDASOURCE/targetroot
-%description
-Panda3D is a game engine which includes graphics, audio, I/O, collision detection, and other abilities relevant to the creation of 3D games. Panda3D is open source and free software under the revised BSD license, and can be used for both free and commercial game development at no financial cost.
-Panda3D's intended game-development language is Python. The engine itself is written in C++, and utilizes an automatic wrapper-generator to expose the complete functionality of the engine in a Python interface.
-
-This package contains the SDK for development with Panda3D, install panda3d-runtime for the runtime files.
-%post
-/sbin/ldconfig
-%postun
-/sbin/ldconfig
-%files
-%defattr(-,root,root)
-/etc/Confauto.prc
-/etc/Config.prc
-/usr/share/panda3d
-/etc/ld.so.conf.d/panda3d.conf
-/usr/%_lib/panda3d
-""" + PYTHON_SITEPACKAGES + """
-/usr/include/panda3d
-"""
-if not PkgSkip("PVIEW"):
-    INSTALLER_SPEC_FILE += \
-"""/usr/share/applications/pview.desktop
-/usr/share/mime-info/panda3d.mime
-/usr/share/mime-info/panda3d.keys
-/usr/share/mime/packages/panda3d.xml
-/usr/share/application-registry/panda3d.applications
-"""
-
-RUNTIME_INSTALLER_SPEC_FILE="""
-Summary: Runtime binary and browser plugin for the Panda3D Game Engine
-Name: panda3d-runtime
-Version: VERSION
-Release: RPMRELEASE
-License: BSD License
-Group: Productivity/Graphics/Other
-BuildRoot: PANDASOURCE/targetroot
-%description
-This package contains the runtime distribution and browser plugin of the Panda3D engine. It allows you view webpages that contain Panda3D content and to run games created with Panda3D that are packaged as .p3d file.
-%files
-%defattr(-,root,root)
-/usr/bin/panda3d
-/usr/%_lib/nppanda3d.so
-/usr/%_lib/mozilla/plugins/nppanda3d.so
-/usr/%_lib/mozilla-firefox/plugins/nppanda3d.so
-/usr/%_lib/xulrunner-addons/plugins/nppanda3d.so
-/usr/share/mime-info/panda3d-runtime.mime
-/usr/share/mime-info/panda3d-runtime.keys
-/usr/share/mime/packages/panda3d-runtime.xml
-/usr/share/application-registry/panda3d-runtime.applications
-/usr/share/applications/*.desktop
-"""
-
-# plist file for Mac OSX
-Info_plist = """<?xml version="1.0" encoding="UTF-8"?>
-<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
-<plist version="1.0">
-<dict>
-  <key>CFBundleIdentifier</key>
-  <string>%(package_id)s</string>
-  <key>CFBundleShortVersionString</key>
-  <string>%(version)s</string>
-  <key>IFPkgFlagRelocatable</key>
-  <false/>
-  <key>IFPkgFlagAuthorizationAction</key>
-  <string>RootAuthorization</string>
-  <key>IFPkgFlagAllowBackRev</key>
-  <true/>
-</dict>
-</plist>
-"""
-
-# FreeBSD pkg-descr
-INSTALLER_PKG_DESCR_FILE = """
-Panda3D is a game engine which includes graphics, audio, I/O, collision detection, and other abilities relevant to the creation of 3D games. Panda3D is open source and free software under the revised BSD license, and can be used for both free and commercial game development at no financial cost.
-Panda3D's intended game-development language is Python. The engine itself is written in C++, and utilizes an automatic wrapper-generator to expose the complete functionality of the engine in a Python interface.
-
-This package contains the SDK for development with Panda3D, install panda3d-runtime for the runtime files.
-
-WWW: https://www.panda3d.org/
-"""
-
-# FreeBSD pkg-descr
-RUNTIME_INSTALLER_PKG_DESCR_FILE = """
-Runtime binary and browser plugin for the Panda3D Game Engine
-
-This package contains the runtime distribution and browser plugin of the Panda3D engine. It allows you view webpages that contain Panda3D content and to run games created with Panda3D that are packaged as .p3d file.
-
-WWW: https://www.panda3d.org/
-"""
-
-# FreeBSD PKG Manifest template file
-INSTALLER_PKG_MANIFEST_FILE = """
-name: NAME
-version: VERSION
-arch: ARCH
-origin: ORIGIN
-comment: "Panda3D free 3D engine SDK"
-www: https://www.panda3d.org
-maintainer: rdb <[email protected]>
-prefix: /usr/local
-flatsize: INSTSIZEMB
-deps: {DEPENDS}
-"""
-
-def MakeInstallerLinux():
-    if not RUNTIME and not PkgSkip("PYTHON"):
-        PYTHONV = SDK["PYTHONVERSION"].rstrip('dmu')
-    else:
-        PYTHONV = "python"
-    PV = PYTHONV.replace("python", "")
-
-    # Clean and set up a directory to install Panda3D into
-    oscmd("rm -rf targetroot data.tar.gz control.tar.gz panda3d.spec")
-    oscmd("mkdir --mode=0755 targetroot")
-
-    dpkg_present = False
-    if os.path.exists("/usr/bin/dpkg-architecture") and os.path.exists("/usr/bin/dpkg-deb"):
-        dpkg_present = True
-    rpmbuild_present = False
-    if os.path.exists("/usr/bin/rpmbuild"):
-        rpmbuild_present = True
-
-    if dpkg_present and rpmbuild_present:
-        Warn("both dpkg and rpmbuild present.")
-
-    if dpkg_present:
-        # Invoke installpanda.py to install it into a temporary dir
-        lib_dir = GetDebLibDir()
-        if RUNTIME:
-            InstallRuntime(destdir="targetroot", prefix="/usr", outputdir=GetOutputDir(), libdir=lib_dir)
-        else:
-            InstallPanda(destdir="targetroot", prefix="/usr", outputdir=GetOutputDir(), libdir=lib_dir)
-            oscmd("chmod -R 755 targetroot/usr/share/panda3d")
-            oscmd("mkdir -m 0755 -p targetroot/usr/share/man/man1")
-            oscmd("install -m 0644 doc/man/*.1 targetroot/usr/share/man/man1/")
-
-        oscmd("dpkg --print-architecture > "+GetOutputDir()+"/tmp/architecture.txt")
-        pkg_arch = ReadFile(GetOutputDir()+"/tmp/architecture.txt").strip()
-        if (RUNTIME):
-            txt = RUNTIME_INSTALLER_DEB_FILE[1:]
-        else:
-            txt = INSTALLER_DEB_FILE[1:]
-        txt = txt.replace("VERSION", DEBVERSION).replace("ARCH", pkg_arch).replace("PV", PV).replace("MAJOR", MAJOR_VERSION)
-        txt = txt.replace("INSTSIZE", str(GetDirectorySize("targetroot") / 1024))
-        oscmd("mkdir --mode=0755 -p targetroot/DEBIAN")
-        oscmd("cd targetroot && (find usr -type f -exec md5sum {} ;) > DEBIAN/md5sums")
-        if (not RUNTIME):
-          oscmd("cd targetroot && (find etc -type f -exec md5sum {} ;) >> DEBIAN/md5sums")
-          WriteFile("targetroot/DEBIAN/conffiles","/etc/Config.prc\n")
-        WriteFile("targetroot/DEBIAN/postinst","#!/bin/sh\necho running ldconfig\nldconfig\n")
-        oscmd("cp targetroot/DEBIAN/postinst targetroot/DEBIAN/postrm")
-
-        # Determine the package name and the locations that
-        # dpkg-shlibdeps should look in for executables.
-        pkg_version = DEBVERSION
-        if RUNTIME:
-            pkg_name = "panda3d-runtime"
-            lib_pattern = "debian/%s/usr/%s/*.so" % (pkg_name, lib_dir)
-        else:
-            pkg_name = "panda3d" + MAJOR_VERSION
-            lib_pattern = "debian/%s/usr/%s/panda3d/*.so*" % (pkg_name, lib_dir)
-        bin_pattern = "debian/%s/usr/bin/*" % (pkg_name)
+if INSTALLER:
+    ProgressOutput(100.0, "Building installer")
+    from makepackage import MakeInstaller
 
-        # dpkg-shlibdeps looks in the debian/{pkg_name}/DEBIAN/shlibs directory
-        # and also expects a debian/control file, so we create this dummy set-up.
-        oscmd("mkdir targetroot/debian")
-        oscmd("ln -s .. targetroot/debian/" + pkg_name)
-        WriteFile("targetroot/debian/control", "")
+    # When using the --installer flag, only install for the current version.
+    python_versions = []
+    if python_version_info:
+        python_versions.append(python_version_info)
 
-        dpkg_shlibdeps = "dpkg-shlibdeps"
-        if GetVerbose():
-            dpkg_shlibdeps += " -v"
+    MakeInstaller(version=VERSION, outputdir=GetOutputDir(),
+                  optimize=GetOptimize(), compressor=COMPRESSOR,
+                  debversion=DEBVERSION, rpmrelease=RPMRELEASE,
+                  runtime=RUNTIME, python_versions=python_versions)
 
-        if RUNTIME:
-            # The runtime doesn't export any useful symbols, so just query the dependencies.
-            oscmd("cd targetroot && %(dpkg_shlibdeps)s -x%(pkg_name)s %(lib_pattern)s %(bin_pattern)s*" % locals())
-            depends = ReadFile("targetroot/debian/substvars").replace("shlibs:Depends=", "").strip()
-            recommends = ""
-        else:
-            pkg_name = "panda3d" + MAJOR_VERSION
-            pkg_dir = "debian/panda3d" + MAJOR_VERSION
-
-            # Generate a symbols file so that other packages can know which symbols we export.
-            oscmd("cd targetroot && dpkg-gensymbols -q -ODEBIAN/symbols -v%(pkg_version)s -p%(pkg_name)s -e%(lib_pattern)s" % locals())
-
-            # Library dependencies are required, binary dependencies are recommended.
-            # We explicitly exclude libphysx-extras since we don't want to depend on PhysX.
-            oscmd("cd targetroot && LD_LIBRARY_PATH=usr/%(lib_dir)s/panda3d %(dpkg_shlibdeps)s -Tdebian/substvars_dep --ignore-missing-info -x%(pkg_name)s -xlibphysx-extras %(lib_pattern)s" % locals())
-            oscmd("cd targetroot && LD_LIBRARY_PATH=usr/%(lib_dir)s/panda3d %(dpkg_shlibdeps)s -Tdebian/substvars_rec --ignore-missing-info -x%(pkg_name)s %(bin_pattern)s" % locals())
-
-            # Parse the substvars files generated by dpkg-shlibdeps.
-            depends = ReadFile("targetroot/debian/substvars_dep").replace("shlibs:Depends=", "").strip()
-            recommends = ReadFile("targetroot/debian/substvars_rec").replace("shlibs:Depends=", "").strip()
-            if PkgSkip("PYTHON")==0:
-                depends += ", " + PYTHONV
-                recommends += ", python-wxversion, python-profiler (>= " + PV + "), python-pmw, python-tk (>= " + PV + ")"
-            if PkgSkip("NVIDIACG")==0:
-                depends += ", nvidia-cg-toolkit"
-
-        # Write back the dependencies, and delete the dummy set-up.
-        txt = txt.replace("DEPENDS", depends.strip(', '))
-        txt = txt.replace("RECOMMENDS", recommends.strip(', '))
-        WriteFile("targetroot/DEBIAN/control", txt)
-        oscmd("rm -rf targetroot/debian")
-
-        # Package it all up into a .deb file.
-        oscmd("chmod -R 755 targetroot/DEBIAN")
-        oscmd("chmod 644 targetroot/DEBIAN/control targetroot/DEBIAN/md5sums")
-        if not RUNTIME:
-            oscmd("chmod 644 targetroot/DEBIAN/conffiles targetroot/DEBIAN/symbols")
-        oscmd("fakeroot dpkg-deb -b targetroot %s_%s_%s.deb" % (pkg_name, pkg_version, pkg_arch))
-
-    elif rpmbuild_present:
-        # Invoke installpanda.py to install it into a temporary dir
-        if RUNTIME:
-            InstallRuntime(destdir="targetroot", prefix="/usr", outputdir=GetOutputDir(), libdir=GetRPMLibDir())
-        else:
-            InstallPanda(destdir="targetroot", prefix="/usr", outputdir=GetOutputDir(), libdir=GetRPMLibDir())
-            oscmd("chmod -R 755 targetroot/usr/share/panda3d")
-
-        oscmd("rpm -E '%_target_cpu' > "+GetOutputDir()+"/tmp/architecture.txt")
-        ARCH = ReadFile(GetOutputDir()+"/tmp/architecture.txt").strip()
-        pandasource = os.path.abspath(os.getcwd())
-
-        if RUNTIME:
-            txt = RUNTIME_INSTALLER_SPEC_FILE[1:]
-        else:
-            txt = INSTALLER_SPEC_FILE[1:]
-            # Add the binaries in /usr/bin explicitly to the spec file
-            for base in os.listdir(GetOutputDir() + "/bin"):
-                txt += "/usr/bin/%s\n" % (base)
-
-        # Write out the spec file.
-        txt = txt.replace("VERSION", VERSION)
-        txt = txt.replace("RPMRELEASE", RPMRELEASE)
-        txt = txt.replace("PANDASOURCE", pandasource)
-        txt = txt.replace("PV", PV)
-        WriteFile("panda3d.spec", txt)
-
-        oscmd("fakeroot rpmbuild --define '_rpmdir "+pandasource+"' --buildroot '"+os.path.abspath("targetroot")+"' -bb panda3d.spec")
-        if (RUNTIME):
-            oscmd("mv "+ARCH+"/panda3d-runtime-"+VERSION+"-"+RPMRELEASE+"."+ARCH+".rpm .")
-        else:
-            oscmd("mv "+ARCH+"/panda3d-"+VERSION+"-"+RPMRELEASE+"."+ARCH+".rpm .")
-        oscmd("rm -rf "+ARCH, True)
-
-    else:
-        exit("To build an installer, either rpmbuild or dpkg-deb must be present on your system!")
-
-def MakeInstallerOSX():
-    if (RUNTIME):
-        # Invoke the make_installer script.
-        AddToPathEnv("DYLD_LIBRARY_PATH", GetOutputDir() + "/plugins")
-        cmdstr = sys.executable + " "
-        if sys.version_info >= (2, 6):
-            cmdstr += "-B "
-
-        cmdstr += "direct/src/plugin_installer/make_installer.py --version %s" % VERSION
-        oscmd(cmdstr)
-        return
-
-    dmg_name = "Panda3D-" + VERSION
-    if not SDK["PYTHONVERSION"].startswith("python2."):
-        dmg_name += '-py' + SDK["PYTHONVERSION"][6:9]
-    dmg_name += ".dmg"
-
-    import compileall
-    if (os.path.isfile(dmg_name)): oscmd("rm -f %s" % dmg_name)
-    if (os.path.exists("dstroot")): oscmd("rm -rf dstroot")
-    if (os.path.exists("Panda3D-rw.dmg")): oscmd('rm -f Panda3D-rw.dmg')
-
-    oscmd("mkdir -p dstroot/base/Developer/Panda3D/lib")
-    oscmd("mkdir -p dstroot/base/Developer/Panda3D/etc")
-    oscmd("cp %s/etc/Config.prc           dstroot/base/Developer/Panda3D/etc/Config.prc" % GetOutputDir())
-    oscmd("cp %s/etc/Confauto.prc         dstroot/base/Developer/Panda3D/etc/Confauto.prc" % GetOutputDir())
-    oscmd("cp -R %s/models                dstroot/base/Developer/Panda3D/models" % GetOutputDir())
-    oscmd("cp -R doc/LICENSE              dstroot/base/Developer/Panda3D/LICENSE")
-    oscmd("cp -R doc/ReleaseNotes         dstroot/base/Developer/Panda3D/ReleaseNotes")
-    oscmd("cp -R %s/Frameworks            dstroot/base/Developer/Panda3D/Frameworks" % GetOutputDir())
-    if os.path.isdir(GetOutputDir()+"/plugins"):
-        oscmd("cp -R %s/plugins           dstroot/base/Developer/Panda3D/plugins" % GetOutputDir())
-
-    # Libraries that shouldn't be in base, but are instead in other modules.
-    no_base_libs = ['libp3ffmpeg', 'libp3fmod_audio', 'libfmodex', 'libfmodexL']
-
-    for base in os.listdir(GetOutputDir()+"/lib"):
-        if not base.endswith(".a") and base.split('.')[0] not in no_base_libs:
-            libname = "dstroot/base/Developer/Panda3D/lib/" + base
-            # We really need to specify -R in order not to follow symlinks
-            # On OSX, just specifying -P is not enough to do that.
-            oscmd("cp -R -P " + GetOutputDir() + "/lib/" + base + " " + libname)
-
-    oscmd("mkdir -p dstroot/tools/Developer/Panda3D/bin")
-    oscmd("mkdir -p dstroot/tools/Developer/Tools")
-    oscmd("ln -s ../Panda3D/bin dstroot/tools/Developer/Tools/Panda3D")
-    oscmd("mkdir -p dstroot/tools/etc/paths.d")
-    # Trailing newline is important, works around a bug in OSX
-    WriteFile("dstroot/tools/etc/paths.d/Panda3D", "/Developer/Panda3D/bin\n")
-
-    oscmd("mkdir -m 0755 -p dstroot/tools/usr/local/share/man/man1")
-    oscmd("install -m 0644 doc/man/*.1 dstroot/tools/usr/local/share/man/man1/")
-
-    for base in os.listdir(GetOutputDir()+"/bin"):
-        binname = "dstroot/tools/Developer/Panda3D/bin/" + base
-        # OSX needs the -R argument to copy symbolic links correctly, it doesn't have -d. How weird.
-        oscmd("cp -R " + GetOutputDir() + "/bin/" + base + " " + binname)
-
-    if PkgSkip("PYTHON")==0:
-        PV = SDK["PYTHONVERSION"][6:9]
-        oscmd("mkdir -p dstroot/pythoncode/usr/local/bin")
-        oscmd("mkdir -p dstroot/pythoncode/Developer/Panda3D/panda3d")
-        oscmd("mkdir -p dstroot/pythoncode/Library/Python/%s/site-packages" % PV)
-        WriteFile("dstroot/pythoncode/Library/Python/%s/site-packages/Panda3D.pth" % PV, "/Developer/Panda3D")
-        oscmd("cp -R %s/pandac                dstroot/pythoncode/Developer/Panda3D/pandac" % GetOutputDir())
-        oscmd("cp -R %s/direct                dstroot/pythoncode/Developer/Panda3D/direct" % GetOutputDir())
-        oscmd("ln -s %s                       dstroot/pythoncode/usr/local/bin/ppython" % SDK["PYTHONEXEC"])
-        oscmd("cp -R %s/*.so                  dstroot/pythoncode/Developer/Panda3D/" % GetOutputDir(), True)
-        oscmd("cp -R %s/*.py                  dstroot/pythoncode/Developer/Panda3D/" % GetOutputDir(), True)
-        if os.path.isdir(GetOutputDir()+"/Pmw"):
-            oscmd("cp -R %s/Pmw               dstroot/pythoncode/Developer/Panda3D/Pmw" % GetOutputDir())
-            compileall.compile_dir("dstroot/pythoncode/Developer/Panda3D/Pmw")
-        WriteFile("dstroot/pythoncode/Developer/Panda3D/direct/__init__.py", "")
-        for base in os.listdir("dstroot/pythoncode/Developer/Panda3D/direct"):
-            if ((base != "extensions") and (base != "extensions_native")):
-                compileall.compile_dir("dstroot/pythoncode/Developer/Panda3D/direct/"+base)
-
-        suffix = GetExtensionSuffix()
-        for base in os.listdir(GetOutputDir()+"/panda3d"):
-            if base.endswith('.py') or (base.endswith(suffix) and '.' not in base[:-len(suffix)]):
-                libname = "dstroot/pythoncode/Developer/Panda3D/panda3d/" + base
-                # We really need to specify -R in order not to follow symlinks
-                # On OSX, just specifying -P is not enough to do that.
-                oscmd("cp -R -P " + GetOutputDir() + "/panda3d/" + base + " " + libname)
-
-    if not PkgSkip("FFMPEG"):
-        oscmd("mkdir -p dstroot/ffmpeg/Developer/Panda3D/lib")
-        oscmd("cp -R %s/lib/libp3ffmpeg.* dstroot/ffmpeg/Developer/Panda3D/lib/" % GetOutputDir())
-
-    #if not PkgSkip("OPENAL"):
-    #    oscmd("mkdir -p dstroot/openal/Developer/Panda3D/lib")
-    #    oscmd("cp -R %s/lib/libp3openal_audio.* dstroot/openal/Developer/Panda3D/lib/" % GetOutputDir())
-
-    if not PkgSkip("FMODEX"):
-        oscmd("mkdir -p dstroot/fmodex/Developer/Panda3D/lib")
-        oscmd("cp -R %s/lib/libp3fmod_audio.* dstroot/fmodex/Developer/Panda3D/lib/" % GetOutputDir())
-        oscmd("cp -R %s/lib/libfmodex* dstroot/fmodex/Developer/Panda3D/lib/" % GetOutputDir())
-
-    oscmd("mkdir -p dstroot/headers/Developer/Panda3D/lib")
-    oscmd("cp -R %s/include               dstroot/headers/Developer/Panda3D/include" % GetOutputDir())
-    if os.path.isfile(GetOutputDir() + "/lib/libp3pystub.a"):
-        oscmd("cp -R -P %s/lib/libp3pystub.a dstroot/headers/Developer/Panda3D/lib/" % GetOutputDir())
-
-    if os.path.isdir("samples"):
-        oscmd("mkdir -p dstroot/samples/Developer/Examples/Panda3D")
-        oscmd("cp -R samples/* dstroot/samples/Developer/Examples/Panda3D/")
-
-    oscmd("chmod -R 0775 dstroot/*")
-    DeleteVCS("dstroot")
-    DeleteBuildFiles("dstroot")
-    # We need to be root to perform a chown. Bleh.
-    # Fortunately PackageMaker does it for us, on 10.5 and above.
-    #oscmd("chown -R root:admin dstroot/*", True)
-
-    oscmd("mkdir -p dstroot/Panda3D/Panda3D.mpkg/Contents/Packages/")
-    oscmd("mkdir -p dstroot/Panda3D/Panda3D.mpkg/Contents/Resources/en.lproj/")
-
-    pkgs = ["base", "tools", "headers"]
-    if not PkgSkip("PYTHON"):    pkgs.append("pythoncode")
-    if not PkgSkip("FFMPEG"):    pkgs.append("ffmpeg")
-    #if not PkgSkip("OPENAL"):    pkgs.append("openal")
-    if not PkgSkip("FMODEX"):    pkgs.append("fmodex")
-    if os.path.isdir("samples"): pkgs.append("samples")
-    for pkg in pkgs:
-        identifier = "org.panda3d.panda3d.%s.pkg" % pkg
-        plist = open("/tmp/Info_plist", "w")
-        plist.write(Info_plist % { "package_id" : identifier, "version" : VERSION })
-        plist.close()
-        if not os.path.isdir("dstroot/" + pkg):
-            os.makedirs("dstroot/" + pkg)
-
-        if OSXTARGET and OSXTARGET <= (10, 5):
-            target = '--target %d.%d' % (OSXTARGET)
-        else:
-            target = ''
-
-        if os.path.exists("/usr/bin/pkgbuild"):
-            # This new package builder is used in Lion and above.
-            cmd = '/usr/bin/pkgbuild --identifier ' + identifier + ' --version ' + VERSION + ' --root dstroot/' + pkg + '/ dstroot/Panda3D/Panda3D.mpkg/Contents/Packages/' + pkg + '.pkg'
-
-        # In older versions, we use PackageMaker.  Apple keeps changing its location.
-        elif os.path.exists("/Developer/usr/bin/packagemaker"):
-            cmd = '/Developer/usr/bin/packagemaker --info /tmp/Info_plist --version ' + VERSION + ' --out dstroot/Panda3D/Panda3D.mpkg/Contents/Packages/' + pkg + '.pkg ' + target + ' --domain system --root dstroot/' + pkg + '/ --no-relocate'
-        elif os.path.exists("/Applications/Xcode.app/Contents/Applications/PackageMaker.app/Contents/MacOS/PackageMaker"):
-            cmd = '/Applications/Xcode.app/Contents/Applications/PackageMaker.app/Contents/MacOS/PackageMaker --info /tmp/Info_plist --version ' + VERSION + ' --out dstroot/Panda3D/Panda3D.mpkg/Contents/Packages/' + pkg + '.pkg ' + target + ' --domain system --root dstroot/' + pkg + '/ --no-relocate'
-        elif os.path.exists("/Developer/Tools/PackageMaker.app/Contents/MacOS/PackageMaker"):
-            cmd = '/Developer/Tools/PackageMaker.app/Contents/MacOS/PackageMaker --info /tmp/Info_plist --version ' + VERSION + ' --out dstroot/Panda3D/Panda3D.mpkg/Contents/Packages/' + pkg + '.pkg ' + target + ' --domain system --root dstroot/' + pkg + '/ --no-relocate'
-        elif os.path.exists("/Developer/Tools/packagemaker"):
-            cmd = '/Developer/Tools/packagemaker -build -f dstroot/' + pkg + '/ -p dstroot/Panda3D/Panda3D.mpkg/Contents/Packages/' + pkg + '.pkg -i /tmp/Info_plist'
-        elif os.path.exists("/Applications/PackageMaker.app/Contents/MacOS/PackageMaker"):
-            cmd = '/Applications/PackageMaker.app/Contents/MacOS/PackageMaker --info /tmp/Info_plist --version ' + VERSION + ' --out dstroot/Panda3D/Panda3D.mpkg/Contents/Packages/' + pkg + '.pkg ' + target + ' --domain system --root dstroot/' + pkg + '/ --no-relocate'
-        else:
-            exit("Neither pkgbuild nor PackageMaker could be found!")
-        oscmd(cmd)
-
-    if os.path.isfile("/tmp/Info_plist"):
-        oscmd("rm -f /tmp/Info_plist")
-
-    # Now that we've built all of the individual packages, build the metapackage.
-    dist = open("dstroot/Panda3D/Panda3D.mpkg/Contents/distribution.dist", "w")
-    dist.write('<?xml version="1.0" encoding="utf-8"?>\n')
-    dist.write('<installer-script minSpecVersion="1.000000" authoringTool="com.apple.PackageMaker" authoringToolVersion="3.0.3" authoringToolBuild="174">\n')
-    dist.write('    <title>Panda3D SDK %s</title>\n' % (VERSION))
-    dist.write('    <options customize="always" allow-external-scripts="no" rootVolumeOnly="false"/>\n')
-    dist.write('    <license language="en" mime-type="text/plain">%s</license>\n' % ReadFile("doc/LICENSE"))
-    dist.write('    <choices-outline>\n')
-    for pkg in pkgs:
-        dist.write('        <line choice="%s"/>\n' % (pkg))
-    dist.write('    </choices-outline>\n')
-    dist.write('    <choice id="base" title="Panda3D Base Installation" description="This package contains the Panda3D libraries, configuration files and models/textures that are needed to use Panda3D. Location: /Developer/Panda3D/" start_enabled="false">\n')
-    dist.write('        <pkg-ref id="org.panda3d.panda3d.base.pkg"/>\n')
-    dist.write('    </choice>\n')
-    dist.write('    <choice id="tools" title="Tools" tooltip="Useful tools and model converters to help with Panda3D development" description="This package contains the various utilities that ship with Panda3D, including packaging tools, model converters, and many more. Location: /Developer/Panda3D/bin/">\n')
-    dist.write('        <pkg-ref id="org.panda3d.panda3d.tools.pkg"/>\n')
-    dist.write('    </choice>\n')
-
-    if not PkgSkip("PYTHON"):
-        dist.write('    <choice id="pythoncode" title="Python Support" tooltip="Python bindings for the Panda3D libraries" description="This package contains the \'direct\', \'pandac\' and \'panda3d\' python packages that are needed to do Python development with Panda3D. Location: /Developer/Panda3D/">\n')
-        dist.write('        <pkg-ref id="org.panda3d.panda3d.pythoncode.pkg"/>\n')
-        dist.write('    </choice>\n')
-
-    if not PkgSkip("FFMPEG"):
-        dist.write('    <choice id="ffmpeg" title="FFMpeg Plug-In" tooltip="FFMpeg video and audio decoding plug-in" description="This package contains the FFMpeg plug-in, which is used for decoding video and audio files with OpenAL.')
-        if PkgSkip("VORBIS") and PkgSkip("OPUS"):
-            dist.write('  It is not required for loading .wav files, which Panda3D can read out of the box.">\n')
-        elif PkgSkip("VORBIS"):
-            dist.write('  It is not required for loading .wav or .opus files, which Panda3D can read out of the box.">\n')
-        elif PkgSkip("OPUS"):
-            dist.write('  It is not required for loading .wav or .ogg files, which Panda3D can read out of the box.">\n')
-        else:
-            dist.write('  It is not required for loading .wav, .ogg or .opus files, which Panda3D can read out of the box.">\n')
-        dist.write('        <pkg-ref id="org.panda3d.panda3d.ffmpeg.pkg"/>\n')
-        dist.write('    </choice>\n')
-
-    #if not PkgSkip("OPENAL"):
-    #    dist.write('    <choice id="openal" title="OpenAL Audio Plug-In" tooltip="OpenAL audio output plug-in" description="This package contains the OpenAL audio plug-in, which is an open-source library for playing sounds.">\n')
-    #    dist.write('        <pkg-ref id="org.panda3d.panda3d.openal.pkg"/>\n')
-    #    dist.write('    </choice>\n')
-
-    if not PkgSkip("FMODEX"):
-        dist.write('    <choice id="fmodex" title="FMOD Ex Plug-In" tooltip="FMOD Ex audio output plug-in" description="This package contains the FMOD Ex audio plug-in, which is a commercial library for playing sounds.  It is an optional component as Panda3D can use the open-source alternative OpenAL instead.">\n')
-        dist.write('        <pkg-ref id="org.panda3d.panda3d.fmodex.pkg"/>\n')
-        dist.write('    </choice>\n')
-
-    if os.path.isdir("samples"):
-        dist.write('    <choice id="samples" title="Sample Programs" tooltip="Python sample programs that use Panda3D" description="This package contains the Python sample programs that can help you with learning how to use Panda3D. Location: /Developer/Examples/Panda3D/">\n')
-        dist.write('        <pkg-ref id="org.panda3d.panda3d.samples.pkg"/>\n')
-        dist.write('    </choice>\n')
-
-    dist.write('    <choice id="headers" title="C++ Header Files" tooltip="Header files for C++ development with Panda3D" description="This package contains the C++ header files that are needed in order to do C++ development with Panda3D. You don\'t need this if you want to develop in Python. Location: /Developer/Panda3D/include/" start_selected="false">\n')
-    dist.write('        <pkg-ref id="org.panda3d.panda3d.headers.pkg"/>\n')
-    dist.write('    </choice>\n')
-    for pkg in pkgs:
-        size = GetDirectorySize("dstroot/" + pkg) // 1024
-        dist.write('    <pkg-ref id="org.panda3d.panda3d.%s.pkg" installKBytes="%d" version="1" auth="Root">file:./Contents/Packages/%s.pkg</pkg-ref>\n' % (pkg, size, pkg))
-    dist.write('</installer-script>\n')
-    dist.close()
-
-    oscmd('hdiutil create Panda3D-rw.dmg -volname "Panda3D SDK %s" -srcfolder dstroot/Panda3D' % (VERSION))
-    oscmd('hdiutil convert Panda3D-rw.dmg -format UDBZ -o %s' % (dmg_name))
-    oscmd('rm -f Panda3D-rw.dmg')
-
-def MakeInstallerFreeBSD():
-    oscmd("rm -rf targetroot +DESC pkg-plist +MANIFEST")
-    oscmd("mkdir targetroot")
-
-    # Invoke installpanda.py to install it into a temporary dir
-    if RUNTIME:
-        InstallRuntime(destdir = "targetroot", prefix = "/usr/local", outputdir = GetOutputDir())
-    else:
-        InstallPanda(destdir = "targetroot", prefix = "/usr/local", outputdir = GetOutputDir())
-
-    if not os.path.exists("/usr/sbin/pkg"):
-        exit("Cannot create an installer without pkg")
-
-    plist_txt = ''
-    for root, dirs, files in os.walk("targetroot/usr/local/", True):
-        for f in files:
-            plist_txt += os.path.join(root, f)[21:] + "\n"
-
-    if not RUNTIME:
-        plist_txt += "@postexec /sbin/ldconfig -m /usr/local/lib/panda3d\n"
-        plist_txt += "@postunexec /sbin/ldconfig -R\n"
-
-        for remdir in ("lib/panda3d", "share/panda3d", "include/panda3d"):
-            for root, dirs, files in os.walk("targetroot/usr/local/" + remdir, False):
-                for d in dirs:
-                    plist_txt += "@dir %s\n" % os.path.join(root, d)[21:]
-            plist_txt += "@dir %s\n" % remdir
-
-    oscmd("echo \"`pkg config abi | tr '[:upper:]' '[:lower:]' | cut -d: -f1,2`:*\" > " + GetOutputDir() + "/tmp/architecture.txt")
-    pkg_arch = ReadFile(GetOutputDir()+"/tmp/architecture.txt").strip()
-
-    dependencies = ''
-    if PkgSkip("PYTHON") == 0:
-        # If this version of Python was installed from a package or ports, let's mark it as dependency.
-        oscmd("rm -f %s/tmp/python_dep" % GetOutputDir())
-        oscmd("pkg query \"\n\t%%n : {\n\t\torigin : %%o,\n\t\tversion : %%v\n\t},\n\" python%s > %s/tmp/python_dep" % (SDK["PYTHONVERSION"][6:9:2], GetOutputDir()), True)
-        if os.path.isfile(GetOutputDir() + "/tmp/python_dep"):
-            python_pkg = ReadFile(GetOutputDir() + "/tmp/python_dep")
-            if python_pkg:
-                dependencies += python_pkg
-
-    manifest_txt = INSTALLER_PKG_MANIFEST_FILE[1:].replace("NAME", 'panda3d' if not RUNTIME else 'panda3d-runtime')
-    manifest_txt = manifest_txt.replace("VERSION", VERSION)
-    manifest_txt = manifest_txt.replace("ARCH", pkg_arch)
-    manifest_txt = manifest_txt.replace("ORIGIN", 'devel/panda3d' if not RUNTIME else 'graphics/panda3d-runtime')
-    manifest_txt = manifest_txt.replace("DEPENDS", dependencies)
-    manifest_txt = manifest_txt.replace("INSTSIZE", str(GetDirectorySize("targetroot") / 1024 / 1024))
-
-    WriteFile("pkg-plist", plist_txt)
-    WriteFile("+DESC", INSTALLER_PKG_DESCR_FILE[1:] if not RUNTIME else RUNTIME_INSTALLER_PKG_DESCR_FILE[1:])
-    WriteFile("+MANIFEST", manifest_txt)
-    oscmd("pkg create -p pkg-plist -r %s  -m . -o . %s" % (os.path.abspath("targetroot"), "--verbose" if GetVerbose() else "--quiet"))
-
-def MakeInstallerAndroid():
-    oscmd("rm -rf apkroot")
-    oscmd("mkdir apkroot")
-
-    # Also remove the temporary apks.
-    apk_unaligned = os.path.join(GetOutputDir(), "tmp", "panda3d-unaligned.apk")
-    apk_unsigned = os.path.join(GetOutputDir(), "tmp", "panda3d-unsigned.apk")
-    if os.path.exists(apk_unaligned):
-        os.unlink(apk_unaligned)
-    if os.path.exists(apk_unsigned):
-        os.unlink(apk_unsigned)
-
-    # Compile the Java classes into a Dalvik executable.
-    dx_cmd = "dx --dex --output=apkroot/classes.dex "
-    if GetOptimize() <= 2:
-        dx_cmd += "--debug "
-    if GetVerbose():
-        dx_cmd += "--verbose "
-    if "ANDROID_API" in SDK:
-        dx_cmd += "--min-sdk-version=%d " % (SDK["ANDROID_API"])
-    dx_cmd += os.path.join(GetOutputDir(), "classes")
-    oscmd(dx_cmd)
-
-    # Copy the libraries one by one.  In case of library dependencies, strip
-    # off any suffix (eg. libfile.so.1.0), as Android does not support them.
-    source_dir = os.path.join(GetOutputDir(), "lib")
-    target_dir = os.path.join("apkroot", "lib", SDK["ANDROID_ABI"])
-    oscmd("mkdir -p %s" % (target_dir))
-
-    # Determine the library directories we should look in.
-    libpath = [source_dir]
-    for dir in os.environ.get("LD_LIBRARY_PATH", "").split(':'):
-        dir = os.path.expandvars(dir)
-        dir = os.path.expanduser(dir)
-        if os.path.isdir(dir):
-            dir = os.path.realpath(dir)
-            if not dir.startswith("/system") and not dir.startswith("/vendor"):
-                libpath.append(dir)
-
-    def copy_library(source, base):
-        # Copy file to destination, stripping version suffix.
-        target = os.path.join(target_dir, base)
-        if not target.endswith('.so'):
-            target = target.rpartition('.so.')[0] + '.so'
-
-        if os.path.isfile(target):
-            # Already processed.
-            return
-
-        oscmd("cp %s %s" % (source, target))
-
-        # Walk through the library dependencies.
-        oscmd("ldd %s | grep .so > %s/tmp/otool-libs.txt" % (target, GetOutputDir()), True)
-        for line in open(GetOutputDir() + "/tmp/otool-libs.txt", "r"):
-            line = line.strip()
-            if not line:
-                continue
-            if '.so.' in line:
-                dep = line.rpartition('.so.')[0] + '.so'
-                oscmd("patchelf --replace-needed %s %s %s" % (line, dep, target), True)
-            else:
-                dep = line
-
-            # Find it on the LD_LIBRARY_PATH.
-            for dir in libpath:
-                fulldep = os.path.join(dir, dep)
-                if os.path.isfile(fulldep):
-                    copy_library(os.path.realpath(fulldep), dep)
-                    break
-
-    # Now copy every lib in the lib dir, and its dependencies.
-    for base in os.listdir(source_dir):
-        if not base.startswith('lib'):
-            continue
-        if not base.endswith('.so') and '.so.' not in base:
-            continue
-
-        source = os.path.join(source_dir, base)
-        if os.path.islink(source):
-            continue
-        copy_library(source, base)
-
-    # Same for Python extension modules.  However, Android is strict about
-    # library naming, so we have a special naming scheme for these, in
-    # conjunction with a custom import hook to find these modules.
-    if not PkgSkip("PYTHON"):
-        suffix = GetExtensionSuffix()
-        source_dir = os.path.join(GetOutputDir(), "panda3d")
-        for base in os.listdir(source_dir):
-            if not base.endswith(suffix):
-                continue
-            modname = base[:-len(suffix)]
-            if '.' not in modname:
-                source = os.path.join(source_dir, base)
-                copy_library(source, "libpy.panda3d.{}.so".format(modname))
-
-        # Same for standard Python modules.
-        import _ctypes
-        source_dir = os.path.dirname(_ctypes.__file__)
-        for base in os.listdir(source_dir):
-            if not base.endswith('.so'):
-                continue
-            modname = base.partition('.')[0]
-            source = os.path.join(source_dir, base)
-            copy_library(source, "libpy.{}.so".format(modname))
-
-    def copy_python_tree(source_root, target_root):
-        for source_dir, dirs, files in os.walk(source_root):
-            if 'site-packages' in dirs:
-                dirs.remove('site-packages')
-
-            if not any(base.endswith('.py') for base in files):
-                continue
-
-            target_dir = os.path.join(target_root, os.path.relpath(source_dir, source_root))
-            target_dir = os.path.normpath(target_dir)
-            os.makedirs(target_dir, 0o755)
-
-            for base in files:
-                if base.endswith('.py'):
-                    target = os.path.join(target_dir, base)
-                    shutil.copy(os.path.join(source_dir, base), target)
-
-    # Copy the Python standard library to the .apk as well.
-    from distutils.sysconfig import get_python_lib
-    stdlib_source = get_python_lib(False, True)
-    stdlib_target = os.path.join("apkroot", "lib", "python{0}.{1}".format(*sys.version_info))
-    copy_python_tree(stdlib_source, stdlib_target)
-
-    # But also copy over our custom site.py.
-    shutil.copy("panda/src/android/site.py", os.path.join(stdlib_target, "site.py"))
-
-    # And now make a site-packages directory containing our direct/panda3d/pandac modules.
-    for tree in "panda3d", "direct", "pandac":
-        copy_python_tree(os.path.join(GetOutputDir(), tree), os.path.join(stdlib_target, "site-packages", tree))
-
-    # Copy the models and config files to the virtual assets filesystem.
-    oscmd("mkdir apkroot/assets")
-    oscmd("cp -R %s apkroot/assets/models" % (os.path.join(GetOutputDir(), "models")))
-    oscmd("cp -R %s apkroot/assets/etc" % (os.path.join(GetOutputDir(), "etc")))
-
-    # Make an empty res folder.  It's needed for the apk to be installable, apparently.
-    oscmd("mkdir apkroot/res")
-
-    # Now package up the application
-    oscmd("cp panda/src/android/pview_manifest.xml apkroot/AndroidManifest.xml")
-    aapt_cmd = "aapt package"
-    aapt_cmd += " -F %s" % (apk_unaligned)
-    aapt_cmd += " -M apkroot/AndroidManifest.xml"
-    aapt_cmd += " -A apkroot/assets -S apkroot/res"
-    aapt_cmd += " -I $PREFIX/share/aapt/android.jar"
-    oscmd(aapt_cmd)
-
-    # And add all the libraries to it.
-    oscmd("cd apkroot && aapt add ../%s classes.dex" % (apk_unaligned))
-    for path, dirs, files in os.walk('apkroot/lib'):
-        if files:
-            rel = os.path.relpath(path, 'apkroot')
-            oscmd("cd apkroot && aapt add ../%s %s/*" % (apk_unaligned, rel))
-
-    # Now align the .apk, which is necessary for Android to load it.
-    oscmd("zipalign -v -p 4 %s %s" % (apk_unaligned, apk_unsigned))
-
-    # Finally, sign it using a debug key.  This is generated if it doesn't exist.
-    oscmd("apksigner debug.ks %s panda3d.apk" % (apk_unsigned))
-
-    # Clean up.
-    oscmd("rm -rf apkroot")
-    os.unlink(apk_unaligned)
-    os.unlink(apk_unsigned)
-
-try:
-    if INSTALLER:
-        ProgressOutput(100.0, "Building installer")
-        target = GetTarget()
-        if target == 'windows':
-            fn = "Panda3D-"
-            dir = "Panda3D-" + VERSION
-
-            if RUNTIME:
-                fn += "Runtime-"
-                title = "Panda3D " + VERSION
-            else:
-                title = "Panda3D SDK " + VERSION
-
-            fn += VERSION
-
-            if not RUNTIME and SDK["PYTHONVERSION"] != "python2.7":
-                fn += '-py' + SDK["PYTHONVERSION"][6:]
-
-            if GetOptimize() <= 2:
-                fn += "-dbg"
-            if GetTargetArch() == 'x64':
-                fn += '-x64'
-                dir += '-x64'
-
-            MakeInstallerNSIS(fn + '.exe', title, 'C:\\' + dir)
-            if not RUNTIME:
-                MakeDebugSymbolArchive(fn + '-pdb.zip', dir)
-        elif (target == 'linux'):
-            MakeInstallerLinux()
-        elif (target == 'darwin'):
-            MakeInstallerOSX()
-        elif (target == 'freebsd'):
-            MakeInstallerFreeBSD()
-        elif (target == 'android'):
-            MakeInstallerAndroid()
-        else:
-            exit("Do not know how to make an installer for this platform")
-
-    if WHEEL:
-        ProgressOutput(100.0, "Building wheel")
-        from makewheel import makewheel
-        makewheel(WHLVERSION, GetOutputDir())
-finally:
-    SaveDependencyCache()
+if WHEEL:
+    ProgressOutput(100.0, "Building wheel")
+    from makewheel import makewheel
+    makewheel(WHLVERSION, GetOutputDir())
 
 ##########################################################################################
 #

+ 3 - 22
makepanda/makepanda.vcproj

@@ -1043,7 +1043,7 @@
 				<File RelativePath="..\panda\src\collide\collisionLine.I"></File>
 				<File RelativePath="..\panda\src\collide\collisionHandlerFluidPusher.h"></File>
 				<File RelativePath="..\panda\src\collide\config_collide.h"></File>
-				<File RelativePath="..\panda\src\collide\collisionTube.h"></File>
+				<File RelativePath="..\panda\src\collide\collisionCapsule.h"></File>
 				<File RelativePath="..\panda\src\collide\collisionSegment.cxx"></File>
 				<File RelativePath="..\panda\src\collide\collisionBox.h"></File>
 				<File RelativePath="..\panda\src\collide\collide_composite1.cxx"></File>
@@ -1054,7 +1054,7 @@
 				<File RelativePath="..\panda\src\collide\collisionNode.h"></File>
 				<File RelativePath="..\panda\src\collide\collisionGeom.I"></File>
 				<File RelativePath="..\panda\src\collide\collisionSegment.I"></File>
-				<File RelativePath="..\panda\src\collide\collisionTube.cxx"></File>
+				<File RelativePath="..\panda\src\collide\collisionCapsule.cxx"></File>
 				<File RelativePath="..\panda\src\collide\collisionHandlerQueue.h"></File>
 				<File RelativePath="..\panda\src\collide\collisionParabola.I"></File>
 				<File RelativePath="..\panda\src\collide\collisionPlane.cxx"></File>
@@ -1089,7 +1089,7 @@
 				<File RelativePath="..\panda\src\collide\collisionInvSphere.cxx"></File>
 				<File RelativePath="..\panda\src\collide\collisionLine.cxx"></File>
 				<File RelativePath="..\panda\src\collide\collisionEntry.I"></File>
-				<File RelativePath="..\panda\src\collide\collisionTube.I"></File>
+				<File RelativePath="..\panda\src\collide\collisionCapsule.I"></File>
 				<File RelativePath="..\panda\src\collide\collisionSolid.h"></File>
 				<File RelativePath="..\panda\src\collide\collisionPlane.I"></File>
 				<File RelativePath="..\panda\src\collide\collisionHandlerGravity.cxx"></File>
@@ -3578,25 +3578,6 @@
 				<File RelativePath="..\panda\src\net\datagram_ui.cxx"></File>
 				<File RelativePath="..\panda\src\net\netAddress.cxx"></File>
 			</Filter>
-			<Filter Name="awesomium">
-				<File RelativePath="..\panda\src\awesomium\WebBrowserTexture.cxx"></File>
-				<File RelativePath="..\panda\src\awesomium\awesomium_includes.h"></File>
-				<File RelativePath="..\panda\src\awesomium\pandaawesomium_composite1.cxx"></File>
-				<File RelativePath="..\panda\src\awesomium\awWebViewListener.cxx"></File>
-				<File RelativePath="..\panda\src\awesomium\awWebViewListener.h"></File>
-				<File RelativePath="..\panda\src\awesomium\awWebCore.h"></File>
-				<File RelativePath="..\panda\src\awesomium\WebBrowserTexture.h"></File>
-				<File RelativePath="..\panda\src\awesomium\awWebCore.cxx"></File>
-				<File RelativePath="..\panda\src\awesomium\AwMouseAndKeyboard.h"></File>
-				<File RelativePath="..\panda\src\awesomium\awWebViewListener.I"></File>
-				<File RelativePath="..\panda\src\awesomium\awWebView.h"></File>
-				<File RelativePath="..\panda\src\awesomium\config_awesomium.h"></File>
-				<File RelativePath="..\panda\src\awesomium\awWebCore.I"></File>
-				<File RelativePath="..\panda\src\awesomium\awWebView.I"></File>
-				<File RelativePath="..\panda\src\awesomium\awWebView.cxx"></File>
-				<File RelativePath="..\panda\src\awesomium\AwMouseAndKeyboard.cxx"></File>
-				<File RelativePath="..\panda\src\awesomium\config_awesomium.cxx"></File>
-			</Filter>
 			<Filter Name="movies">
 				<File RelativePath="..\panda\src\movies\ffmpegVideoCursor.h"></File>
 				<File RelativePath="..\panda\src\movies\movies_composite.cxx"></File>

+ 70 - 1
makepanda/makepandacore.py

@@ -3383,6 +3383,71 @@ def FindLocation(fn, ipath, pyabi=None):
     ORIG_EXT[loc] = ext
     return loc
 
+
+########################################################################
+##
+## These files maintain a python_versions.json file in the built/tmp
+## directory that can be used by the other scripts in this directory.
+##
+########################################################################
+
+
+def GetCurrentPythonVersionInfo():
+    if PkgSkip("PYTHON"):
+        return
+
+    from distutils.sysconfig import get_python_lib
+    return {
+        "version": SDK["PYTHONVERSION"][6:9],
+        "soabi": GetPythonABI(),
+        "ext_suffix": GetExtensionSuffix(),
+        "executable": sys.executable,
+        "purelib": get_python_lib(False),
+        "platlib": get_python_lib(True),
+    }
+
+
+def UpdatePythonVersionInfoFile(new_info):
+    import json
+
+    json_file = os.path.join(GetOutputDir(), "tmp", "python_versions.json")
+    json_data = []
+    if os.path.isfile(json_file) and not PkgSkip("PYTHON"):
+        try:
+            json_data = json.load(open(json_file, 'r'))
+        except:
+            json_data = []
+
+        # Prune the list by removing the entries that conflict with our build,
+        # plus the entries that no longer exist
+        for version_info in json_data[:]:
+            core_pyd = os.path.join(GetOutputDir(), "panda3d", "core" + version_info["ext_suffix"])
+            if version_info["ext_suffix"] == new_info["ext_suffix"] or \
+               version_info["soabi"] == new_info["soabi"] or \
+               not os.path.isfile(core_pyd):
+                json_data.remove(version_info)
+
+    if not PkgSkip("PYTHON"):
+        json_data.append(new_info)
+
+    if VERBOSE:
+        print("Writing %s" % (json_file))
+    json.dump(json_data, open(json_file, 'w'), indent=4)
+
+
+def ReadPythonVersionInfoFile():
+    import json
+
+    json_file = os.path.join(GetOutputDir(), "tmp", "python_versions.json")
+    if os.path.isfile(json_file):
+        try:
+            return json.load(open(json_file, 'r'))
+        except:
+            pass
+
+    return []
+
+
 ########################################################################
 ##
 ## TargetAdd
@@ -3513,11 +3578,15 @@ def TargetAdd(target, dummy=0, opts=[], input=[], dep=[], ipath=None, winrc=None
     if winrc and GetTarget() == 'windows':
         TargetAdd(target, input=WriteResourceFile(target.split("/")[-1].split(".")[0], **winrc))
 
-    if target.endswith(".in"):
+    ext = os.path.splitext(target)[1]
+    if ext == ".in":
         if not CrossCompiling():
             t.deps[FindLocation("interrogate.exe", [])] = 1
         t.deps[FindLocation("dtool_have_python.dat", [])] = 1
 
+    if ext in (".obj", ".tlb", ".res", ".plugin", ".app") or ext in SUFFIX_DLL or ext in SUFFIX_LIB:
+        t.deps[FindLocation("platform.dat", [])] = 1
+
     if target.endswith(".obj") and any(x.endswith(".in") for x in input):
         if not CrossCompiling():
             t.deps[FindLocation("interrogate_module.exe", [])] = 1

+ 171 - 53
makepanda/makewheel.py

@@ -24,14 +24,6 @@ from makepandacore import ColorText, LocateBinary, ParsePandaVersion, GetExtensi
 from base64 import urlsafe_b64encode
 
 
-default_platform = get_platform()
-
-if default_platform.startswith("linux-"):
-    # Is this manylinux1?
-    if (os.path.isfile("/lib/libc-2.5.so") or os.path.isfile("/lib64/libc-2.5.so")) and os.path.isdir("/opt/python"):
-        default_platform = default_platform.replace("linux", "manylinux1")
-
-
 def get_abi_tag():
     if sys.version_info >= (3, 0):
         soabi = get_config_var('SOABI')
@@ -68,26 +60,32 @@ def is_elf_file(path):
            open(path, 'rb').read(4) == b'\x7FELF'
 
 
-def is_mach_o_file(path):
+def is_macho_or_fat_file(path):
     base = os.path.basename(path)
     return os.path.isfile(path) and '.' not in base and \
-           open(path, 'rb').read(4) in (b'\xCA\xFE\xBA\xBE', b'\xBE\xBA\xFE\bCA',
-                                        b'\xFE\xED\xFA\xCE', b'\xCE\xFA\xED\xFE',
-                                        b'\xFE\xED\xFA\xCF', b'\xCF\xFA\xED\xFE')
+           open(path, 'rb').read(4) in (b'\xFE\xED\xFA\xCE', b'\xCE\xFA\xED\xFE',
+                                        b'\xFE\xED\xFA\xCF', b'\xCF\xFA\xED\xFE',
+                                        b'\xCA\xFE\xBA\xBE', b'\xBE\xBA\xFE\xCA',
+                                        b'\xCA\xFE\xBA\xBF', b'\xBF\xBA\xFE\xCA')
 
 def is_fat_file(path):
     return os.path.isfile(path) and \
-           open(path, 'rb').read(4) in (b'\xCA\xFE\xBA\xBE', b'\xBE\xBA\xFE\bCA')
+           open(path, 'rb').read(4) in (b'\xCA\xFE\xBA\xBE', b'\xBE\xBA\xFE\xCA',
+                                        b'\xCA\xFE\xBA\xBF', b'\xBF\xBA\xFE\xCA')
+
+
+def get_python_ext_module_dir():
+    import _ctypes
+    return os.path.dirname(_ctypes.__file__)
 
 
 if sys.platform in ('win32', 'cygwin'):
     is_executable = is_exe_file
 elif sys.platform == 'darwin':
-    is_executable = is_mach_o_file
+    is_executable = is_macho_or_fat_file
 else:
     is_executable = is_elf_file
 
-
 # Other global parameters
 PY_VERSION = "cp{0}{1}".format(*sys.version_info)
 ABI_TAG = get_abi_tag()
@@ -96,6 +94,24 @@ EXCLUDE_EXT = [".pyc", ".pyo", ".N", ".prebuilt", ".xcf", ".plist", ".vcproj", "
 # Plug-ins to install.
 PLUGIN_LIBS = ["pandagl", "pandagles", "pandagles2", "pandadx9", "p3tinydisplay", "p3ptloader", "p3assimp", "p3ffmpeg", "p3openal_audio", "p3fmod_audio"]
 
+# Libraries included in manylinux ABI that should be ignored.  See PEP 513/571.
+MANYLINUX_LIBS = [
+    "libgcc_s.so.1", "libstdc++.so.6", "libm.so.6", "libdl.so.2", "librt.so.1",
+    "libcrypt.so.1", "libc.so.6", "libnsl.so.1", "libutil.so.1",
+    "libpthread.so.0", "libresolv.so.2", "libX11.so.6", "libXext.so.6",
+    "libXrender.so.1", "libICE.so.6", "libSM.so.6", "libGL.so.1",
+    "libgobject-2.0.so.0", "libgthread-2.0.so.0", "libglib-2.0.so.0",
+
+    # These are not mentioned in manylinux1 spec but should nonetheless always
+    # be excluded.
+    "linux-vdso.so.1", "linux-gate.so.1", "ld-linux.so.2",
+]
+
+# Binaries to never scan for dependencies on non-Windows systems.
+IGNORE_UNIX_DEPS_OF = [
+    "panda3d_tools/pstats",
+]
+
 WHEEL_DATA = """Wheel-Version: 1.0
 Generator: makepanda
 Root-Is-Purelib: false
@@ -265,6 +281,7 @@ class WheelFile(object):
         # Used to locate dependency libraries.
         self.lib_path = []
         self.dep_paths = {}
+        self.ignore_deps = set()
 
     def consider_add_dependency(self, target_path, dep, search_path=None):
         """Considers adding a dependency library.
@@ -277,29 +294,38 @@ class WheelFile(object):
 
         self.dep_paths[dep] = None
 
-        if dep.lower().startswith("python") or os.path.basename(dep).startswith("libpython"):
-            # Don't include the Python library.
+        if dep in self.ignore_deps or dep.lower().startswith("python") or os.path.basename(dep).startswith("libpython"):
+            # Don't include the Python library, or any other explicit ignore.
+            if GetVerbose():
+                print("Ignoring {0} (explicitly ignored)".format(dep))
             return
 
         if sys.platform == "darwin" and dep.endswith(".so"):
             # Temporary hack for 1.9, which had link deps on modules.
             return
 
-        source_path = None
+        if sys.platform == "darwin" and dep.startswith("/System/"):
+            return
+
+        if dep.startswith('/'):
+            source_path = dep
+        else:
+            source_path = None
 
-        if search_path is None:
-            search_path = self.lib_path
+            if search_path is None:
+                search_path = self.lib_path
 
-        for lib_dir in search_path:
-            # Ignore static stuff.
-            path = os.path.join(lib_dir, dep)
-            if os.path.isfile(path):
-                source_path = os.path.normpath(path)
-                break
+            for lib_dir in search_path:
+                # Ignore static stuff.
+                path = os.path.join(lib_dir, dep)
+                if os.path.isfile(path):
+                    source_path = os.path.normpath(path)
+                    break
 
         if not source_path:
             # Couldn't find library in the panda3d lib dir.
-            #print("Ignoring %s" % (dep))
+            if GetVerbose():
+                print("Ignoring {0} (not in search path)".format(dep))
             return
 
         self.dep_paths[dep] = target_path
@@ -309,20 +335,19 @@ class WheelFile(object):
     def write_file(self, target_path, source_path):
         """Adds the given file to the .whl file."""
 
+        orig_source_path = source_path
+
         # If this is a .so file, we should set the rpath appropriately.
         temp = None
-        ext = os.path.splitext(source_path)[1]
+        basename, ext = os.path.splitext(source_path)
         if ext in ('.so', '.dylib') or '.so.' in os.path.basename(source_path) or \
             (not ext and is_executable(source_path)):
-            # Scan and add Unix dependencies.
-            deps = scan_dependencies(source_path)
-            for dep in deps:
-                # Only include dependencies with relative path.  Otherwise we
-                # end up overwriting system files like /lib/ld-linux.so.2!
-                # Yes, it happened to me.
-                if '/' not in dep:
-                    target_dep = os.path.dirname(target_path) + '/' + dep
-                    self.consider_add_dependency(target_dep, dep)
+
+            # Scan Unix dependencies.
+            if target_path not in IGNORE_UNIX_DEPS_OF:
+                deps = scan_dependencies(source_path)
+            else:
+                deps = []
 
             suffix = ''
             if '.so' in os.path.basename(source_path):
@@ -333,7 +358,10 @@ class WheelFile(object):
             temp = tempfile.NamedTemporaryFile(suffix=suffix, prefix='whl', delete=False)
 
             # On macOS, if no fat wheel was requested, extract the right architecture.
-            if sys.platform == "darwin" and is_fat_file(source_path) and not self.platform.endswith("_intel"):
+            if sys.platform == "darwin" and is_fat_file(source_path) \
+                and not self.platform.endswith("_intel") \
+                and "_fat" not in self.platform:
+
                 if self.platform.endswith("_x86_64"):
                     arch = 'x86_64'
                 else:
@@ -343,26 +371,55 @@ class WheelFile(object):
                 # Otherwise, just copy it over.
                 temp.write(open(source_path, 'rb').read())
 
-            os.fchmod(temp.fileno(), os.fstat(temp.fileno()).st_mode | 0o111)
             temp.close()
+            os.chmod(temp.name, os.stat(temp.name).st_mode | 0o711)
 
-            # Fix things like @loader_path/../lib references
+            # Now add dependencies.  On macOS, fix @loader_path references.
             if sys.platform == "darwin":
+                if source_path.endswith('deploy-stubw'):
+                    deps_path = '@executable_path/../Frameworks'
+                else:
+                    deps_path = '@loader_path'
                 loader_path = [os.path.dirname(source_path)]
                 for dep in deps:
-                    if '@loader_path' not in dep:
+                    if dep.endswith('/Python'):
+                        # If this references the Python framework, change it
+                        # to reference libpython instead.
+                        new_dep = deps_path + '/libpython{0}.{1}.dylib'.format(*sys.version_info)
+
+                    elif '@loader_path' in dep:
+                        dep_path = dep.replace('@loader_path', '.')
+                        target_dep = os.path.dirname(target_path) + '/' + os.path.basename(dep)
+                        target_dep = self.consider_add_dependency(target_dep, dep_path, loader_path)
+                        if not target_dep:
+                            # It won't be included, so no use adjusting the path.
+                            continue
+                        new_dep = os.path.join(deps_path, os.path.relpath(target_dep, os.path.dirname(target_path)))
+
+                    elif dep.startswith('/Library/Frameworks/Python.framework/'):
+                        # Add this dependency if it's in the Python directory.
+                        target_dep = os.path.dirname(target_path) + '/' + os.path.basename(dep)
+                        target_dep = self.consider_add_dependency(target_dep, dep, loader_path)
+                        if not target_dep:
+                            # It won't be included, so no use adjusting the path.
+                            continue
+                        new_dep = os.path.join(deps_path, os.path.relpath(target_dep, os.path.dirname(target_path)))
+
+                    else:
+                        if '/' in dep:
+                            if GetVerbose():
+                                print("Ignoring dependency %s" % (dep))
                         continue
 
-                    dep_path = dep.replace('@loader_path', '.')
-                    target_dep = os.path.dirname(target_path) + '/' + os.path.basename(dep)
-                    target_dep = self.consider_add_dependency(target_dep, dep_path, loader_path)
-                    if not target_dep:
-                        # It won't be included, so no use adjusting the path.
-                        continue
-
-                    new_dep = os.path.join('@loader_path', os.path.relpath(target_dep, os.path.dirname(target_path)))
                     subprocess.call(["install_name_tool", "-change", dep, new_dep, temp.name])
             else:
+                # On other unixes, we just add dependencies normally.
+                for dep in deps:
+                    # Only include dependencies with relative path, for now.
+                    if '/' not in dep:
+                        target_dep = os.path.dirname(target_path) + '/' + dep
+                        self.consider_add_dependency(target_dep, dep)
+
                 subprocess.call(["strip", "-s", temp.name])
                 subprocess.call(["patchelf", "--set-rpath", "$ORIGIN", temp.name])
 
@@ -392,7 +449,7 @@ class WheelFile(object):
         self.records.append("{0},sha256={1},{2}\n".format(target_path, digest, size))
 
         if GetVerbose():
-            print("Adding %s from %s" % (target_path, source_path))
+            print("Adding {0} from {1}".format(target_path, orig_source_path))
         self.zip_file.write(source_path, target_path)
 
         #if temp:
@@ -433,11 +490,24 @@ class WheelFile(object):
         self.zip_file.close()
 
 
-def makewheel(version, output_dir, platform=default_platform):
+def makewheel(version, output_dir, platform=None):
     if sys.platform not in ("win32", "darwin") and not sys.platform.startswith("cygwin"):
         if not LocateBinary("patchelf"):
             raise Exception("patchelf is required when building a Linux wheel.")
 
+    if platform is None:
+        # Determine the platform from the build.
+        platform_dat = os.path.join(output_dir, 'tmp', 'platform.dat')
+        if os.path.isfile(platform_dat):
+            platform = open(platform_dat, 'r').read().strip()
+        else:
+            print("Could not find platform.dat in build directory")
+            platform = get_platform()
+            if platform.startswith("linux-"):
+                # Is this manylinux1?
+                if os.path.isfile("/lib/libc-2.5.so") and os.path.isdir("/opt/python"):
+                    platform = platform.replace("linux", "manylinux1")
+
     platform = platform.replace('-', '_').replace('.', '_')
 
     # Global filepaths
@@ -483,6 +553,21 @@ def makewheel(version, output_dir, platform=default_platform):
     whl = WheelFile('panda3d', version, platform)
     whl.lib_path = [libs_dir]
 
+    if sys.platform == "win32":
+        whl.lib_path.append(join(output_dir, "python", "DLLs"))
+
+    if platform.startswith("manylinux"):
+        # On manylinux1, we pick up all libraries except for the ones specified
+        # by the manylinux1 ABI.
+        whl.lib_path.append("/usr/local/lib")
+
+        if platform.endswith("_x86_64"):
+            whl.lib_path += ["/lib64", "/usr/lib64"]
+        else:
+            whl.lib_path += ["/lib", "/usr/lib"]
+
+        whl.ignore_deps.update(MANYLINUX_LIBS)
+
     # Add the trees with Python modules.
     whl.write_directory('direct', direct_dir)
 
@@ -493,6 +578,7 @@ def makewheel(version, output_dir, platform=default_platform):
 __version__ = '{0}'
 """.format(version))
 
+    # Copy the extension modules from the panda3d directory.
     ext_suffix = GetExtensionSuffix()
 
     for file in os.listdir(panda3d_dir):
@@ -509,6 +595,23 @@ __version__ = '{0}'
 
             whl.write_file(target_path, source_path)
 
+    # And copy the extension modules from the Python installation into the
+    # deploy_libs directory, for use by deploy-ng.
+    ext_suffix = '.pyd' if sys.platform in ('win32', 'cygwin') else '.so'
+    ext_mod_dir = get_python_ext_module_dir()
+
+    for file in os.listdir(ext_mod_dir):
+        if file.endswith(ext_suffix):
+            source_path = os.path.join(ext_mod_dir, file)
+
+            if file.endswith('.pyd') and platform.startswith('cygwin'):
+                # Rename it to .dll for cygwin Python to be able to load it.
+                target_path = 'deploy_libs/' + os.path.splitext(file)[0] + '.dll'
+            else:
+                target_path = 'deploy_libs/' + file
+
+            whl.write_file(target_path, source_path)
+
     # Add plug-ins.
     for lib in PLUGIN_LIBS:
         plugin_name = 'lib' + lib
@@ -540,7 +643,7 @@ __version__ = '{0}'
     # Add a panda3d-tools directory containing the executables.
     entry_points = '[console_scripts]\n'
     entry_points += 'eggcacher = direct.directscripts.eggcacher:main\n'
-    entry_points += 'pfreeze = direct.showutil.pfreeze:main\n'
+    entry_points += 'pfreeze = direct.dist.pfreeze:main\n'
     tools_init = ''
     for file in os.listdir(bin_dir):
         basename = os.path.splitext(file)[0]
@@ -557,6 +660,9 @@ __version__ = '{0}'
             funcname = basename.replace('-', '_')
             entry_points += '{0} = panda3d_tools:{1}\n'.format(basename, funcname)
             tools_init += '{0} = lambda: _exec_tool({1!r})\n'.format(funcname, file)
+    entry_points += '[distutils.commands]\n'
+    entry_points += 'build_apps = direct.dist.commands:build_apps\n'
+    entry_points += 'bdist_apps = direct.dist.commands:bdist_apps\n'
 
     whl.write_file_data('panda3d_tools/__init__.py', PANDA3D_TOOLS_INIT.format(tools_init))
 
@@ -570,6 +676,18 @@ __version__ = '{0}'
     whl.write_file(info_dir + '/README.md', readme_src)
     whl.write_file_data(info_dir + '/top_level.txt', 'direct\npanda3d\npandac\npanda3d_tools\n')
 
+    # Add libpython for deployment
+    if sys.platform in ('win32', 'cygwin'):
+        pylib_name = 'python{0}{1}.dll'.format(*sys.version_info)
+        pylib_path = os.path.join(get_config_var('BINDIR'), pylib_name)
+    elif sys.platform == 'darwin':
+        pylib_name = 'libpython{0}.{1}.dylib'.format(*sys.version_info)
+        pylib_path = os.path.join(get_config_var('LIBDIR'), pylib_name)
+    else:
+        pylib_name = get_config_var('LDLIBRARY')
+        pylib_path = os.path.join(get_config_var('LIBDIR'), pylib_name)
+    whl.write_file('deploy_libs/' + pylib_name, pylib_path)
+
     whl.close()
 
 
@@ -580,7 +698,7 @@ if __name__ == "__main__":
     parser.add_option('', '--version', dest = 'version', help = 'Panda3D version number (default: %s)' % (version), default = version)
     parser.add_option('', '--outputdir', dest = 'outputdir', help = 'Makepanda\'s output directory (default: built)', default = 'built')
     parser.add_option('', '--verbose', dest = 'verbose', help = 'Enable verbose output', action = 'store_true', default = False)
-    parser.add_option('', '--platform', dest = 'platform', help = 'Override platform tag (default: %s)' % (default_platform), default = get_platform())
+    parser.add_option('', '--platform', dest = 'platform', help = 'Override platform tag', default = None)
     (options, args) = parser.parse_args()
 
     SetVerbose(options.verbose)

+ 1 - 1
makepanda/test_imports.py

@@ -62,6 +62,7 @@ import direct.directutil.LargeBlobSenderConsts
 import direct.directutil.Mopath
 import direct.directutil.Verify
 import direct.directutil.WeightedChoice
+import direct.dist.FreezeTool
 import direct.distributed.AsyncRequest
 import direct.distributed.CRCache
 import direct.distributed.CRDataCache
@@ -234,7 +235,6 @@ import direct.showbase.VFSImporter
 import direct.showbase.WxGlobal
 import direct.showutil.BuildGeometry
 import direct.showutil.Effects
-import direct.showutil.FreezeTool
 import direct.showutil.Rope
 import direct.showutil.TexMemWatcher
 import direct.showutil.TexViewer

+ 1 - 1
panda/src/audiotraits/openalAudioSound.cxx

@@ -44,7 +44,7 @@ OpenALAudioSound(OpenALAudioManager* manager,
   _loops_completed(0),
   _source(0),
   _manager(manager),
-  _volume(manager->get_volume()),
+  _volume(1.0f),
   _balance(0),
   _play_rate(1.0),
   _positional(positional),

+ 0 - 47
panda/src/awesomium/AwMouseAndKeyboard.cxx

@@ -1,47 +0,0 @@
-/**
- * PANDA 3D SOFTWARE
- * Copyright (c) Carnegie Mellon University.  All rights reserved.
- *
- * All use of this software is subject to the terms of the revised BSD
- * license.  You should have received a copy of this license along
- * with this source code in a file named "LICENSE."
- *
- * @file AwMouseAndKeyboard.cxx
- * @author Bei Yang
- * @date 2010-03
- */
-
-#include "config_awesomium.h"
-#include "AwMouseAndKeyboard.h"
-#include "dataNodeTransmit.h"
-
-TypeHandle AwMouseAndKeyboard::_type_handle;
-
-AwMouseAndKeyboard::AwMouseAndKeyboard(const std::string &name):
-DataNode(name)
-{
-  _button_events_input = define_input("button_events", ButtonEventList::get_class_type());
-  _button_events_output = define_output("button_events", ButtonEventList::get_class_type());
-}
-
-
-void AwMouseAndKeyboard::do_transmit_data(DataGraphTraverser *trav, const DataNodeTransmit &input, DataNodeTransmit &output){
-
-  if (input.has_data(_button_events_input)) {
-    const ButtonEventList *button_events;
-    DCAST_INTO_V(button_events, input.get_data(_button_events_input).get_ptr());
-
-    int num_events = button_events->get_num_events();
-    for (int i = 0; i < num_events; i++) {
-      const ButtonEvent &be = button_events->get_event(i);
-      std::string event_name = be._button.get_name();
-      printf("Button Event! : %s with code %i and index %i ", event_name.c_str(), be._keycode, be._button.get_index());
-      if(be._type == ButtonEvent::T_down) printf("down");
-      if(be._type == ButtonEvent::T_repeat) printf("repeat");
-      if(be._type == ButtonEvent::T_up) printf("up");
-      if(be._type == ButtonEvent::T_resume_down) printf("T_resume_down");
-      printf("\n");
-    }
-  }
-
-}

+ 0 - 8
panda/src/awesomium/ReadMe.txt

@@ -1,8 +0,0 @@
-See http://www.sirikata.com/wiki/index.php?title=Compiling_Awesomium
-
-But the Chromium revision 22725.
-The awesomium git repository used is at: http://github.com/pathorn/awesomium/network
-The version of awesomium is from the master August 7 checkin, tree fc3239b4f49031285682cb5d78256d56e9001b66
-
-Any method that had a wstring in the parameter, a second method was created which just accepts all std::string. This fixes the VC7 - VC9 linker error.
-

+ 0 - 194
panda/src/awesomium/WebBrowserTexture.cxx

@@ -1,194 +0,0 @@
-/**
- * PANDA 3D SOFTWARE
- * Copyright (c) Carnegie Mellon University.  All rights reserved.
- *
- * All use of this software is subject to the terms of the revised BSD
- * license.  You should have received a copy of this license along
- * with this source code in a file named "LICENSE."
- *
- * @file WebBrowserTexture.cxx
- * @author bei yang
- * @date 2010-03
- */
-
-#include "config_awesomium.h"
-#include "WebBrowserTexture.h"
-
-TypeHandle WebBrowserTexture::_type_handle;
-
-/**
- * Copy constructor for web browser texture.  The behavior of copying a
- * webtexture is that will be the same as a standard texture copy.  However,
- * the content will remain the system until set_web_view is called.
- */
-WebBrowserTexture::WebBrowserTexture(const WebBrowserTexture &copy):
-Texture(copy)
-{
-    // this kind of assumes that the previous texture was initialized properly
-    _aw_web_view = copy._aw_web_view;
-    _update_active = copy._update_active;
-    _flip_texture_active = copy._flip_texture_active;
-}
-
-/**
- * This initializes a web browser texture with the given AwWebView class.
- */
-WebBrowserTexture::WebBrowserTexture(const std::string &name, AwWebView* aw_web_view):
-Texture(name),
-_update_active(true),
-_flip_texture_active(false)
-{
-    set_web_view(aw_web_view);
-    set_minfilter(FT_linear);
-    set_magfilter(FT_linear);
-
-}
-
-/**
- * Standard destructor... doesn't do anything.  All destructing happens in
- * parent texture class.
- */
-WebBrowserTexture::~WebBrowserTexture()
-{
-    // do nothing
-}
-
-
-/**
- * Standard destructor... doesn't do anything.  All destructing happens in
- * parent texture class.
- */
-bool WebBrowserTexture::get_keep_ram_image() const {
-    return true;
-}
-
-/**
- * A WebBrowserTexture must always keep its ram image.  This is essentially a
- * sub.
- */
-void WebBrowserTexture::do_reload_ram_image() {
-    // A MovieTexture should never dump its RAM image.  Therefore, this is not
-    // needed.
-}
-
-/**
- * Should be overridden by derived classes to return true if cull_callback()
- * has been defined.  Otherwise, returns false to indicate cull_callback()
- * does not need to be called for this node during the cull traversal.
- *
- * This one returns true because it uses the cull traverser method to do the
- * texture udpate.
- */
-bool WebBrowserTexture::has_cull_callback() const {
-    return true;
-}
-
-/**
- * Sets the internal AwWebView of this texture.  After calling this, the
- * texture will automatically set it's width and height to match the AwWebView
- * at the next time it is culled and rendered.
- */
-void WebBrowserTexture::set_web_view(AwWebView* aw_web_view){
-    _aw_web_view = aw_web_view;
-}
-
-
-/**
- * Gets the current internal AwWebView of this texture.
- */
-AwWebView* WebBrowserTexture::get_web_view() const{
-    return _aw_web_view;
-}
-
-
-/**
- * Gives the ability to toggle updating this texture or not.  This can be
- * disabled to improve performance so that only the one that needs to be
- * active is active.
- */
-void WebBrowserTexture::set_update_active(bool active_flag){
-    _update_active = active_flag;
-}
-
-
-/**
- * Gets whether or not this texture is updating itself every time it is
- * rendered.
- */
-bool WebBrowserTexture::get_update_active() const{
-    return _update_active;
-}
-
-
-/**
- * This toggles on/off automatic flipping of the of the texture at a source
- * level.  Awesomium renders things that are flipped vertically.  This enables
- * automatic flipping of that.
- *
- * Since it is doing byte manipulation, this can get rather slow.  Turning
- * this on should be avoided.  Instead, flipping should be taken care of via
- * UV coordinates or shaders.
- */
-void WebBrowserTexture::set_flip_texture_active(bool active_flag){
-    _flip_texture_active = active_flag;
-}
-
-/**
- * Returns whether automatic texture flipping is enabled.
- */
-bool WebBrowserTexture::get_flip_texture_active() const {
-    return _flip_texture_active;
-}
-
-
-/**
- * This function will be called during the cull traversal to update the
- * WebBrowserTexture.  This method calls the render method of AwWebView but
- * does not call the update method of AwWebCore.
- */
-bool WebBrowserTexture::cull_callback(CullTraverser *trav, const CullTraverserData &data) const{
-    // see if we are in a state where udpates can happen.  else just return
-    if( !_update_active ) return true;
-    if( _aw_web_view == nullptr ) return true;
-
-    // do we even need to update?
-    if( !_aw_web_view->is_dirty() ) return true;
-
-    // see if we're the same size, if not we need to make sure this texture
-    // matches the webview
-    if( _aw_web_view->get_width() != get_x_size() || _aw_web_view->get_height() != get_y_size() || get_texture_type() != TT_2d_texture){
-        // these casts are so dirty especially when the method itself is
-        // labled as const.  Really Texture::cull_callback should be not const
-        // first clean up
-        ((WebBrowserTexture*)this)->clear_ram_mipmap_images();
-        ((WebBrowserTexture*)this)->clear_ram_image();
-        // now set up the texture again
-        ((WebBrowserTexture*)this)->setup_2d_texture( _aw_web_view->get_width(), _aw_web_view->get_height(), T_unsigned_byte, F_rgba );
-        // should be good to go at this point
-    }
-
-    // get the pointer
-    PTA_uchar ram_image = ((WebBrowserTexture*)this)->modify_ram_image();
-    unsigned char* cp_data = ram_image.p();
-    // render it
-    _aw_web_view->render((void*)cp_data, get_x_size()*4, 4);
-
-    if(_flip_texture_active){
-        // flips the texture around... this is super slow.  Really this should
-        // never be enabled.  However beginners might find this useful
-        size_t width = get_x_size();
-        size_t height = get_y_size();
-        for(size_t i=0; i < height/2; i++){
-            for(size_t j=0; j < width; j++){
-                unsigned char tmp[4];
-                size_t a_pos = j+width*i;
-                size_t b_pos = j + width*(height-i-1);
-                memcpy(tmp,&cp_data[4*a_pos], 4); //tmp = a
-                memcpy(&cp_data[4*a_pos], &cp_data[4*b_pos], 4); //a = b
-                memcpy(&cp_data[4*b_pos], tmp, 4); //b = tmp
-            }
-        }
-    }
-    // success
-    return true;
-}

+ 0 - 77
panda/src/awesomium/WebBrowserTexture.h

@@ -1,77 +0,0 @@
-/**
- * PANDA 3D SOFTWARE
- * Copyright (c) Carnegie Mellon University.  All rights reserved.
- *
- * All use of this software is subject to the terms of the revised BSD
- * license.  You should have received a copy of this license along
- * with this source code in a file named "LICENSE."
- *
- * @file WebBrowserTexture.h
- * @author Bei Yang
- * @date 2010-08-03
- */
-
-#ifndef WebBrowserTexture_H
-#define WebBrowserTexture_H
-
-#include "pandabase.h"
-#include "texture.h"
-#include "awWebView.h"
-
-
-/**
- * A Wrapper class for Awesomium webview.  This implements most of Awesomium's
- * features and updates on the cull_traverser callback much much like a movie
- * texture.
- *
- * The use of class means that you will have to follow Awesomium license
- * agreement give below http://www.khrona.com/products/awesomium/licensing
- */
-class EXPCL_PANDAAWESOMIUM WebBrowserTexture : public Texture {
-protected:
-    AwWebView* _aw_web_view;
-    bool _update_active;
-    bool _flip_texture_active;
-
-// Constructors & Destructors ------------
-private:
-    WebBrowserTexture(const WebBrowserTexture &copy);
-PUBLISHED:
-    WebBrowserTexture(const std::string &name, AwWebView* aw_web_view = nullptr);
-    virtual ~WebBrowserTexture();
-
-
-// methods --------------
-protected:
-    bool get_keep_ram_image() const;
-    void do_reload_ram_image();
-public:
-    virtual bool has_cull_callback() const;
-    virtual bool cull_callback(CullTraverser *trav, const CullTraverserData &data) const;
-PUBLISHED:
-    void set_web_view(AwWebView* aw_web_view);
-    AwWebView* get_web_view() const;
-    void set_update_active(bool active_flag);
-    bool get_update_active() const;
-    void set_flip_texture_active(bool active_flag);
-    bool get_flip_texture_active() const;
-
-// Type handles ----------------
-public:
-    static TypeHandle get_class_type() {
-        return _type_handle;
-    }
-    static void init_type() {
-        Texture::init_type();
-        register_type(_type_handle, "WebBrowserTexture",
-            Texture::get_class_type());
-    }
-    virtual TypeHandle get_type() const {
-        return get_class_type();
-    }
-    virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
-
-private:
-    static TypeHandle _type_handle;
-};
-#endif

+ 0 - 48
panda/src/awesomium/awWebCore.I

@@ -1,48 +0,0 @@
-/**
- * PANDA 3D SOFTWARE
- * Copyright (c) Carnegie Mellon University.  All rights reserved.
- *
- * All use of this software is subject to the terms of the revised BSD
- * license.  You should have received a copy of this license along
- * with this source code in a file named "LICENSE."
- *
- * @file awWebCore.I
- * @author rurbino
- * @date 2009-10-12
- */
-
-INLINE void AwWebCore::
-setBaseDirectory(const std::string& baseDirectory) {
-  WebCore::setBaseDirectory(baseDirectory);
-}
-
-INLINE void AwWebCore::
-setCustomResponsePage(int statusCode, const std::string& filePath) {
-  WebCore::setCustomResponsePage(statusCode, filePath);
-}
-
-INLINE void AwWebCore::
-update() {
-  WebCore::update();
-}
-
-INLINE const std::string& AwWebCore::
-getBaseDirectory() const {
-  return WebCore::getBaseDirectory();
-}
-
-INLINE bool AwWebCore::
-arePluginsEnabled() const {
-  return WebCore::arePluginsEnabled();
-}
-
-
-INLINE void AwWebCore::
-pause(){
-  WebCore::pause();
-}
-
-INLINE void AwWebCore::
-resume() {
-  WebCore::resume();
-}

+ 0 - 56
panda/src/awesomium/awWebCore.cxx

@@ -1,56 +0,0 @@
-/**
- * PANDA 3D SOFTWARE
- * Copyright (c) Carnegie Mellon University.  All rights reserved.
- *
- * All use of this software is subject to the terms of the revised BSD
- * license.  You should have received a copy of this license along
- * with this source code in a file named "LICENSE."
- *
- * @file awWebCore.cxx
- * @author rurbino
- * @date 2009-10-12
- */
-
-#include "config_awesomium.h"
-#include "awWebCore.h"
-
-#include <WebCore.h>
-
-TypeHandle AwWebCore::_type_handle;
-
-AwWebCore::
-AwWebCore(AwWebCore::LogLevel level, bool enablePlugins , AwWebCore::PixelFormat pixelFormat)
-#ifndef CPPPARSER
-:
-  WebCore(static_cast<Awesomium::LogLevel>(level), enablePlugins, static_cast<Awesomium::PixelFormat>(pixelFormat))
-#endif
-  {
-  awesomium_cat.info() << "constructing webcore\n";
-}
-
-AwWebCore::
-~AwWebCore() {
-  awesomium_cat.info() << "destructor webcore\n";
-}
-
-Awesomium::WebCore& AwWebCore::
-Get() {
-  return WebCore::Get();
-}
-
-Awesomium::WebCore* AwWebCore::
-GetPointer() {
-  return WebCore::GetPointer();
-}
-
-AwWebView *  AwWebCore::
-createWebView(int width, int height, bool isTransparent , bool enableAsyncRendering , int maxAsyncRenderPerSec ) {
-  Awesomium::WebView * newView = WebCore::createWebView(width, height, isTransparent, enableAsyncRendering, maxAsyncRenderPerSec);
-  AwWebView * result = new AwWebView(newView);
-  return result;
-}
-
-AwWebCore::PixelFormat AwWebCore::
-getPixelFormat() const {
-  return ( static_cast<AwWebCore::PixelFormat>( WebCore::getPixelFormat()) );
-}

+ 0 - 94
panda/src/awesomium/awWebCore.h

@@ -1,94 +0,0 @@
-/**
- * PANDA 3D SOFTWARE
- * Copyright (c) Carnegie Mellon University.  All rights reserved.
- *
- * All use of this software is subject to the terms of the revised BSD
- * license.  You should have received a copy of this license along
- * with this source code in a file named "LICENSE."
- *
- * @file awWebCore.h
- * @author rurbino
- * @date 2009-10-12
- */
-
-#ifndef AWWEBCORE_H
-#define AWWEBCORE_H
-
-#include "pandabase.h"
-#include "typedReferenceCount.h"
-#include "luse.h"
-
-#include "awesomium_includes.h"
-
-class AwWebView;
-/**
- * Thin wrappings arround WebCore.h
- */
-class EXPCL_PANDAAWESOMIUM AwWebCore : public TypedReferenceCount, public Awesomium::WebCore {
-PUBLISHED:
-  /**
-  * An enumeration of the three verbosity settings for the Awesomium Log.
-  */
-  enum LogLevel
-  {
-    LOG_NONE ,      // No log is created
-    LOG_NORMAL ,        // Logs only errors
-    LOG_VERBOSE         // Logs everything
-  };
-
-  /**
-  * An enumeration of the two output pixel formats that WebView::render will use.
-  */
-  enum PixelFormat
-  {
-    PF_BGRA,    // BGRA byte ordering [Blue, Green, Red, Alpha]
-    PF_RGBA     // RGBA byte ordering [Red, Green, Blue, Alpha]
-  };
-
-  AwWebCore(LogLevel level = LOG_NORMAL, bool enablePlugins = true, PixelFormat pixelFormat = PF_BGRA);
-
-  virtual ~AwWebCore();
-
-  static Awesomium::WebCore& Get();
-
-  static Awesomium::WebCore* GetPointer();
-
-  INLINE void setBaseDirectory(const std::string& baseDirectory);
-
-  AwWebView * createWebView(int width, int height, bool isTransparent = false, bool enableAsyncRendering = false, int maxAsyncRenderPerSec = 70);
-
-  INLINE void setCustomResponsePage(int statusCode, const std::string& filePath);
-
-  INLINE void update();
-
-  INLINE const std::string& getBaseDirectory() const;
-
-  AwWebCore::PixelFormat getPixelFormat() const;
-
-  INLINE bool arePluginsEnabled() const;
-
-  INLINE void pause();
-
-  INLINE void resume();
-
-public:
-  static TypeHandle get_class_type() {
-    return _type_handle;
-  }
-  static void init_type() {
-    TypedReferenceCount::init_type();
-    register_type(_type_handle, "AwWebCore",
-                  TypedReferenceCount::get_class_type());
-  }
-  virtual TypeHandle get_type() const {
-    return get_class_type();
-  }
-  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
-
-private:
-  static TypeHandle _type_handle;
-};
-
-#include "awWebCore.I"
-
-#endif

+ 0 - 82
panda/src/awesomium/awWebView.I

@@ -1,82 +0,0 @@
-/**
- * PANDA 3D SOFTWARE
- * Copyright (c) Carnegie Mellon University.  All rights reserved.
- *
- * All use of this software is subject to the terms of the revised BSD
- * license.  You should have received a copy of this license along
- * with this source code in a file named "LICENSE."
- *
- * @file awWebView.I
- * @author rurbino
- * @date 2009-10-12
- */
-
-INLINE void AwWebView::
-destroy(void)
-{
-  _myWebView->destroy();
-}
-
-
-INLINE void AwWebView::
-setListener(Awesomium::WebViewListener *  listener) {
-  _myWebView->setListener(listener);
-}
-
-INLINE Awesomium::WebViewListener* AwWebView::
-getListener() {
-  return _myWebView->getListener();
-}
-
-INLINE void AwWebView::
-goToHistoryOffset(int offset) {
-  _myWebView->goToHistoryOffset(offset);
-}
-
-INLINE void AwWebView::
-executeJavascript2(const std::string& javascript, const std::string& frameName ) {
-  _myWebView->executeJavascript2(javascript, frameName);
-}
-
-INLINE Awesomium::FutureJSValue AwWebView::
-executeJavascriptWithResult2(const std::string& javascript, const std::string& frameName ) {
-  return _myWebView->executeJavascriptWithResult2(javascript, frameName);
-}
-
-INLINE void AwWebView::
-setProperty(const std::string& name, const Awesomium::JSValue& value) {
-  _myWebView->setProperty(name, value);
-}
-
-INLINE void AwWebView::
-setCallback(const std::string& name) {
-  _myWebView->setCallback(name);
-}
-
-INLINE bool AwWebView::
-isDirty() {
-  return _myWebView->isDirty();
-}
-
-INLINE void AwWebView::
-render( size_t  destination, int destRowSpan, int destDepth)
-{
-  _myWebView->render( reinterpret_cast<unsigned char *>(destination), destRowSpan, destDepth, 0);
-}
-
-
-
-INLINE void AwWebView::
-injectMouseUp(AwWebView::MouseButton button) {
-  _myWebView->injectMouseUp(static_cast<Awesomium::MouseButton>(button));
-}
-
-INLINE void AwWebView::
-injectMouseWheelXY(int scrollAmountX, int scrollAmountY){
-  _myWebView->injectMouseWheelXY(scrollAmountX, scrollAmountY);
-}
-
-INLINE void AwWebView::
-injectKeyEvent(bool press, int modifiers, int windowsCode, int nativeCode) {
-  _myWebView->injectKeyEvent(press, modifiers, windowsCode, nativeCode);
-}

+ 0 - 74
panda/src/awesomium/awWebView.cxx

@@ -1,74 +0,0 @@
-/**
- * PANDA 3D SOFTWARE
- * Copyright (c) Carnegie Mellon University.  All rights reserved.
- *
- * All use of this software is subject to the terms of the revised BSD
- * license.  You should have received a copy of this license along
- * with this source code in a file named "LICENSE."
- *
- * @file awWebView.cxx
- * @author rurbino
- * @date 2009-10-12
- */
-
-#include "config_awesomium.h"
-#include "awWebView.h"
-
-TypeHandle AwWebView::_type_handle;
-
-AwWebView::
-AwWebView(Awesomium::WebView * webViewPtr)  {
-  _myWebView = webViewPtr;
-
-}
-
-AwWebView::
-~AwWebView() {
-}
-
-
-void AwWebView::
-loadURL2(const std::string& url, const std::string& frameName , const std::string& username , const std::string& password )
-{
-  _myWebView->loadURL2(url, frameName, username, password);
-
-}
-
-void AwWebView::
-loadHTML2(const std::string& html, const std::string& frameName )
-{
-  _myWebView->loadHTML2(html, frameName);
-}
-
-
-void AwWebView::
-loadFile2(const std::string& file, const std::string& frameName )
-{
-  _myWebView->loadFile2(file, frameName);
-}
-
-
-void AwWebView::
-render(size_t destination, int destRowSpan, int destDepth, AwWebView::Rect * renderedRect) {
-  if (renderedRect) {
-    Awesomium::Rect rect(renderedRect->x, renderedRect->y, renderedRect->width, renderedRect->height);
-    _myWebView->Awesomium::WebView::render( reinterpret_cast<unsigned char *>(destination), destRowSpan, destDepth, &rect);
-  }
-  else
-  {
-    AwWebView::render(destination, destRowSpan, destDepth, 0);
-  }
-}
-
-void AwWebView::
-injectMouseDown(AwWebView::MouseButton button) {
-  awesomium_cat.debug() <<"got mouse down " << button << "\n";
-  _myWebView->injectMouseDown(static_cast<Awesomium::MouseButton>(button));
-}
-
-
-void AwWebView::
-injectMouseMove(int x, int y) {
-  // awesomium_cat.debug() <<"got mouse move " << x << " " << y << "\n";
-  _myWebView->injectMouseMove(x,y);
-}

+ 0 - 131
panda/src/awesomium/awWebView.h

@@ -1,131 +0,0 @@
-/**
- * PANDA 3D SOFTWARE
- * Copyright (c) Carnegie Mellon University.  All rights reserved.
- *
- * All use of this software is subject to the terms of the revised BSD
- * license.  You should have received a copy of this license along
- * with this source code in a file named "LICENSE."
- *
- * @file awWebView.h
- * @author rurbino
- * @date 2009-10-12
- */
-
-#ifndef AWWEBVIEW_H
-#define AWWEBVIEW_H
-
-#include "pandabase.h"
-#include "typedReferenceCount.h"
-#include "luse.h"
-
-#include "awesomium_includes.h"
-
-class WebViewListener;
-
-/**
- * Thin bindings, wraps a WebView * returned from WebCore.createWebView
- */
-class EXPCL_PANDAAWESOMIUM AwWebView : public TypedReferenceCount{
-PUBLISHED:
-
-  /**
-  * Mouse button enumerations, used with WebView::injectMouseDown
-  * and WebView::injectMouseUp
-  */
-  enum MouseButton {
-    LEFT_MOUSE_BTN,
-    MIDDLE_MOUSE_BTN,
-    RIGHT_MOUSE_BTN
-  };
-
-  /**
-   * A simple rectangle class, used with WebView::render
-   */
-  struct Rect {
-    int x, y, width, height;
-
-    Rect();
-    Rect(int x, int y, int width, int height);
-    bool isEmpty() const;
-  };
-
-
-PUBLISHED:
-  AwWebView(Awesomium::WebView * webView);
-
-  virtual ~AwWebView();
-
-  INLINE void destroy(void);
-
-  INLINE void setListener(Awesomium::WebViewListener * listener);
-
-  INLINE Awesomium::WebViewListener* getListener();
-
-  // VC7 linker doesn't like wstring from VS2008, hence using the all regular
-  // string version
-  void loadURL2(const std::string& url, const std::string& frameName ="", const std::string& username="" , const std::string& password="");
-
-  // VC7 linker doesn't like wstring from VS2008, hence using the all regular
-  // string version
-  void loadHTML2(const std::string& html, const std::string& frameName = "");
-
-  // VC7 linker doesn't like wstring from VS2008, hence using the all regular
-  // string version
-  void loadFile2(const std::string& file, const std::string& frameName = "" );
-
-  INLINE void goToHistoryOffset(int offset);
-
-  // VC7 linker doesn't like wstring from VS2008, hence using the all regular
-  // string version
-  INLINE void executeJavascript2(const std::string& javascript, const std::string& frameName = "" );
-
-  INLINE Awesomium::FutureJSValue executeJavascriptWithResult2(const std::string& javascript, const std::string& frameName = "");
-
-  INLINE void setProperty(const std::string& name, const Awesomium::JSValue& value);
-
-  INLINE void setCallback(const std::string& name);
-
-  INLINE bool isDirty();
-
-  INLINE void render(size_t destination, int destRowSpan, int destDepth);
-
-  void render(size_t destination, int destRowSpan, int destDepth, AwWebView::Rect * renderedRect);
-
-  void injectMouseMove(int x, int y);
-
-  void injectMouseDown(AwWebView::MouseButton button);
-
-  INLINE void injectMouseUp(AwWebView::MouseButton button);
-
-  INLINE void injectMouseWheelXY(int scrollAmountX, int scrollAmountY);
-
-  INLINE void injectMouseWheel(int scrollAmountY) {
-    injectMouseWheelXY(0, scrollAmountY);
-  }
-
-  INLINE void injectKeyEvent(bool press, int modifiers, int windowsCode, int nativeCode=0);
-
-private:
-  Awesomium::WebView * _myWebView;
-
-public:
-  static TypeHandle get_class_type() {
-    return _type_handle;
-  }
-  static void init_type() {
-    TypedReferenceCount::init_type();
-    register_type(_type_handle, "AwWebView",
-                  TypedReferenceCount::get_class_type());
-  }
-  virtual TypeHandle get_type() const {
-    return get_class_type();
-  }
-  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
-
-private:
-  static TypeHandle _type_handle;
-};
-
-#include "awWebView.I"
-
-#endif

+ 0 - 80
panda/src/awesomium/awWebViewListener.cxx

@@ -1,80 +0,0 @@
-/**
- * PANDA 3D SOFTWARE
- * Copyright (c) Carnegie Mellon University.  All rights reserved.
- *
- * All use of this software is subject to the terms of the revised BSD
- * license.  You should have received a copy of this license along
- * with this source code in a file named "LICENSE."
- *
- * @file awWebViewListener.cxx
- * @author rurbino
- * @date 2009-10-12
- */
-
-#include "config_awesomium.h"
-#include "awWebViewListener.h"
-
-TypeHandle AwWebViewListener::_type_handle;
-
-AwWebViewListener::
-AwWebViewListener()  {
-  awesomium_cat.info() << "constructing WebViewListner" ;
-}
-
-void AwWebViewListener::onBeginNavigation(const std::string& url, const std::wstring& frameName){
-}
-
-void AwWebViewListener::onBeginLoading(const std::string& url, const std::wstring& frameName, int statusCode, const std::wstring& mimeType) {
-  awesomium_cat.info() << "onBeginLoading" ;
-}
-
-    /**
-    * This event is fired when all loads have finished for a WebView.
-    */
-void AwWebViewListener::onFinishLoading() {
-}
-
-    /**
-    * This event is fired when a Client callback has been invoked via Javascript from a page.
-    *
-    * @param    name    The name of the client callback that was invoked (specifically, "Client._this_name_here_(...)").
-    *
-    * @param    args    The arguments passed to the callback.
-    */
-void AwWebViewListener::onCallback(const std::string& name, const Awesomium::JSArguments& args) {
-}
-
-    /**
-    * This event is fired when a page title is received.
-    *
-    * @param    title   The page title.
-    *
-    * @param    frameName   The name of the frame that this event originated from.
-    */
-void AwWebViewListener::onReceiveTitle(const std::wstring& title, const std::wstring& frameName) {
-}
-
-    /**
-    * This event is fired when a tooltip has changed state.
-    *
-    * @param    tooltip     The tooltip text (or, is an empty string when the tooltip should disappear).
-    */
-void AwWebViewListener::onChangeTooltip(const std::wstring& tooltip) {
-}
-
-    /**
-    * This event is fired when keyboard focus has changed.
-    *
-    * @param    isFocused   Whether or not the keyboard is currently focused.
-    */
-void AwWebViewListener::onChangeKeyboardFocus(bool isFocused) {
-}
-
-    /**
-    * This event is fired when the target URL has changed. This is usually the result of
-    * hovering over a link on the page.
-    *
-    * @param    url The updated target URL (or empty if the target URL is cleared).
-    */
-void AwWebViewListener::onChangeTargetURL(const std::string& url) {
-}

+ 0 - 123
panda/src/awesomium/awWebViewListener.h

@@ -1,123 +0,0 @@
-/**
- * PANDA 3D SOFTWARE
- * Copyright (c) Carnegie Mellon University.  All rights reserved.
- *
- * All use of this software is subject to the terms of the revised BSD
- * license.  You should have received a copy of this license along
- * with this source code in a file named "LICENSE."
- *
- * @file awWebViewListener.h
- * @author rurbino
- * @date 2009-10-12
- */
-
-#ifndef AWWEBVIEWLISTENER_H
-#define AWWEBVIEWLISTENER_H
-
-#include "pandabase.h"
-#include "typedReferenceCount.h"
-#include "luse.h"
-
-#include "awesomium_includes.h"
-
-/**
- * Thin bindings, wraps a WebViewListener
- */
-class EXPCL_PANDAAWESOMIUM AwWebViewListener : public TypedReferenceCount, public Awesomium::WebCore {
-PUBLISHED:
-
-
-PUBLISHED:
-        AwWebViewListener();
-
-        virtual ~AwWebViewListener() {}
-
-    /**
-    * This event is fired when a WebView begins navigating to a new URL.
-    *
-    * @param    url     The URL that is being navigated to.
-    *
-    * @param    frameName   The name of the frame that this event originated from.
-    */
-    void onBeginNavigation(const std::string& url, const std::wstring& frameName) ;
-
-    /**
-    * This event is fired when a WebView begins to actually receive data from a server.
-    *
-    * @param    url     The URL of the frame that is being loaded.
-    *
-    * @param    frameName   The name of the frame that this event originated from.
-    *
-    * @param    statusCode  The HTTP status code returned by the server.
-    *
-    * @param    mimeType    The mime-type of the content that is being loaded.
-    */
-        void onBeginLoading(const std::string& url, const std::wstring& frameName, int statusCode, const std::wstring& mimeType);
-
-    /**
-    * This event is fired when all loads have finished for a WebView.
-    */
-        void onFinishLoading();
-
-    /**
-    * This event is fired when a Client callback has been invoked via Javascript from a page.
-    *
-    * @param    name    The name of the client callback that was invoked (specifically, "Client._this_name_here_(...)").
-    *
-    * @param    args    The arguments passed to the callback.
-    */
-  void onCallback(const std::string& name, const Awesomium::JSArguments& args);
-
-    /**
-    * This event is fired when a page title is received.
-    *
-    * @param    title   The page title.
-    *
-    * @param    frameName   The name of the frame that this event originated from.
-    */
-  void onReceiveTitle(const std::wstring& title, const std::wstring& frameName) ;
-
-    /**
-    * This event is fired when a tooltip has changed state.
-    *
-    * @param    tooltip     The tooltip text (or, is an empty string when the tooltip should disappear).
-    */
-    void onChangeTooltip(const std::wstring& tooltip);
-
-    /**
-    * This event is fired when keyboard focus has changed.
-    *
-    * @param    isFocused   Whether or not the keyboard is currently focused.
-    */
-    void onChangeKeyboardFocus(bool isFocused) ;
-
-    /**
-    * This event is fired when the target URL has changed. This is usually the result of
-    * hovering over a link on the page.
-    *
-    * @param    url The updated target URL (or empty if the target URL is cleared).
-    */
-    void onChangeTargetURL(const std::string& url) ;
-
-
-public:
-  static TypeHandle get_class_type() {
-    return _type_handle;
-  }
-  static void init_type() {
-    TypedReferenceCount::init_type();
-    register_type(_type_handle, "AwWebViewListener",
-                  TypedReferenceCount::get_class_type());
-  }
-  virtual TypeHandle get_type() const {
-    return get_class_type();
-  }
-  virtual TypeHandle force_init_type() {init_type(); return get_class_type();}
-
-private:
-  static TypeHandle _type_handle;
-};
-
-// #include "awWebViewListener.I"
-
-#endif

+ 0 - 21
panda/src/awesomium/awesomium_includes.h

@@ -1,21 +0,0 @@
-/**
- * PANDA 3D SOFTWARE
- * Copyright (c) Carnegie Mellon University.  All rights reserved.
- *
- * All use of this software is subject to the terms of the revised BSD
- * license.  You should have received a copy of this license along
- * with this source code in a file named "LICENSE."
- *
- * @file awesomium_includes.h
- * @author rurbino
- * @date 2010-10-08
- */
-
-#ifndef _AWESOMIUM_INCLUDES_H_
-#define _AWESOMIUM_INCLUDES_H_
-
-#include <WebCore.h>
-#include <WebView.h>
-#include <WebViewListener.h>
-
-#endif

+ 0 - 51
panda/src/awesomium/config_awesomium.cxx

@@ -1,51 +0,0 @@
-/**
- * PANDA 3D SOFTWARE
- * Copyright (c) Carnegie Mellon University.  All rights reserved.
- *
- * All use of this software is subject to the terms of the revised BSD
- * license.  You should have received a copy of this license along
- * with this source code in a file named "LICENSE."
- *
- * @file config_awesomium.cxx
- * @author rurbino
- * @date 2009-10-12
- */
-
-#include "config_awesomium.h"
-#include "awWebCore.h"
-#include "awWebView.h"
-#include "awWebViewListener.h"
-#include "dconfig.h"
-
-
-#if !defined(CPPPARSER) && !defined(LINK_ALL_STATIC) && !defined(BUILDING_PANDAAWESOMIUM)
-  #error Buildsystem error: BUILDING_PANDAAWESOMIUM not defined
-#endif
-
-Configure(config_awesomium);
-NotifyCategoryDef(awesomium, "");
-
-
-ConfigureFn(config_awesomium) {
-  init_libawesomium();
-}
-
-/**
- * Initializes the library.  This must be called at least once before any of
- * the functions or classes in this library can be used.  Normally it will be
- * called by the static initializers and need not be called explicitly, but
- * special cases exist.
- */
-void
-init_libawesomium() {
-  static bool initialized = false;
-  if (initialized) {
-    return;
-  }
-  initialized = true;
-
-  AwWebCore::init_type();
-  AwWebView::init_type();
-  AwWebViewListener::init_type();
-
-}

+ 0 - 26
panda/src/awesomium/config_awesomium.h

@@ -1,26 +0,0 @@
-/**
- * PANDA 3D SOFTWARE
- * Copyright (c) Carnegie Mellon University.  All rights reserved.
- *
- * All use of this software is subject to the terms of the revised BSD
- * license.  You should have received a copy of this license along
- * with this source code in a file named "LICENSE."
- *
- * @file config_awesomium.h
- * @author rurbino
- * @date 2009-10-12
- */
-
-#ifndef CONFIG_AWESOMIUM_H
-#define CONFIG_AWESOMIUM_H
-
-#include "pandabase.h"
-#include "notifyCategoryProxy.h"
-
-#include "dconfig.h"
-
-NotifyCategoryDecl(awesomium, EXPCL_PANDAAWESOMIUM, EXPTP_PANDAAWESOMIUM);
-
-extern EXPCL_PANDAAWESOMIUM void init_libawesomium();
-
-#endif /* CONFIG_AWESOMIUM_H */

+ 0 - 4
panda/src/awesomium/pandaawesomium_composite1.cxx

@@ -1,4 +0,0 @@
-#include "config_awesomium.cxx"
-#include "awWebCore.cxx"
-#include "awWebView.cxx"
-#include "awWebViewListener.cxx"

+ 6 - 6
panda/src/bullet/bulletBodyNode.cxx

@@ -28,7 +28,7 @@
 #include "collisionPlane.h"
 #include "collisionSphere.h"
 #include "collisionPolygon.h"
-#include "collisionTube.h"
+#include "collisionCapsule.h"
 
 TypeHandle BulletBodyNode::_type_handle;
 
@@ -813,12 +813,12 @@ add_shapes_from_collision_solids(CollisionNode *cnode) {
       do_add_shape(BulletBoxShape::make_from_solid(box), ts);
     }
 
-    // CollisionTube
-    else if (CollisionTube::get_class_type() == type) {
-      CPT(CollisionTube) tube = DCAST(CollisionTube, solid);
-      CPT(TransformState) ts = TransformState::make_pos((tube->get_point_b() + tube->get_point_a()) / 2.0);
+    // CollisionCapsule
+    else if (CollisionCapsule::get_class_type() == type) {
+      CPT(CollisionCapsule) capsule = DCAST(CollisionCapsule, solid);
+      CPT(TransformState) ts = TransformState::make_pos((capsule->get_point_b() + capsule->get_point_a()) / 2.0);
 
-      do_add_shape(BulletCapsuleShape::make_from_solid(tube), ts);
+      do_add_shape(BulletCapsuleShape::make_from_solid(capsule), ts);
     }
 
     // CollisionPlane

+ 6 - 6
panda/src/bullet/bulletCapsuleShape.cxx

@@ -87,16 +87,16 @@ ptr() const {
 
 /**
  * Constructs a new BulletCapsuleShape using the information from a
- * CollisionTube from the builtin collision system.
+ * CollisionCapsule from the builtin collision system.
  */
 BulletCapsuleShape *BulletCapsuleShape::
-make_from_solid(const CollisionTube *solid) {
-  
+make_from_solid(const CollisionCapsule *solid) {
+
   PN_stdfloat radius = solid->get_radius();
-  // CollisionTube height includes the hemispheres, Bullet only wants the cylinder height.
-  PN_stdfloat height = (solid->get_point_b() - solid->get_point_a()).length() - (radius * 2);
+  // Get capsule's cylinder height: length from point A to point B
+  PN_stdfloat height = (solid->get_point_b() - solid->get_point_a()).length();
 
-  // CollisionTubes are always Z-Up.
+  // CollisionCapsules are always Z-Up.
   return new BulletCapsuleShape(radius, height, Z_up);
 }
 

+ 2 - 2
panda/src/bullet/bulletCapsuleShape.h

@@ -20,7 +20,7 @@
 #include "bullet_utils.h"
 #include "bulletShape.h"
 
-#include "collisionTube.h"
+#include "collisionCapsule.h"
 
 /**
  *
@@ -35,7 +35,7 @@ PUBLISHED:
   BulletCapsuleShape(const BulletCapsuleShape &copy);
   INLINE ~BulletCapsuleShape();
 
-  static BulletCapsuleShape *make_from_solid(const CollisionTube *solid);
+  static BulletCapsuleShape *make_from_solid(const CollisionCapsule *solid);
 
   INLINE PN_stdfloat get_radius() const;
   INLINE PN_stdfloat get_half_height() const;

+ 6 - 3
panda/src/bullet/bulletVehicle.cxx

@@ -235,10 +235,13 @@ void BulletVehicle::
 do_sync_b2p() {
 
   for (int i=0; i < _vehicle->getNumWheels(); i++) {
-    // synchronize the wheels with the (interpolated) chassis worldtransform
-    _vehicle->updateWheelTransform(i, true);
+    btWheelInfo &info = _vehicle->getWheelInfo(i);
 
-    btWheelInfo info = _vehicle->getWheelInfo(i);
+    // synchronize the wheels with the (interpolated) chassis worldtransform.
+    // It resets the m_isInContact flag, so restore that afterwards.
+    bool in_contact = info.m_raycastInfo.m_isInContact;
+    _vehicle->updateWheelTransform(i, true);
+    info.m_raycastInfo.m_isInContact = in_contact;
 
     PandaNode *node = (PandaNode *)info.m_clientInfo;
     if (node) {

+ 8 - 0
panda/src/cocoadisplay/cocoaGraphicsStateGuardian.h

@@ -20,6 +20,7 @@
 
 #import <AppKit/NSOpenGL.h>
 #import <OpenGL/OpenGL.h>
+#import <CoreVideo/CoreVideo.h>
 
 /**
  * A tiny specialization on GLGraphicsStateGuardian to add some Cocoa-specific
@@ -38,14 +39,21 @@ public:
                              CocoaGraphicsStateGuardian *share_with);
 
   virtual ~CocoaGraphicsStateGuardian();
+  bool setup_vsync();
 
   INLINE void lock_context();
   INLINE void unlock_context();
 
   NSOpenGLContext *_share_context;
   NSOpenGLContext *_context;
+  NSOpenGLPixelFormat *_format = nullptr;
   FrameBufferProperties _fbprops;
 
+  CVDisplayLinkRef _display_link = nullptr;
+  TrueMutexImpl _swap_lock;
+  TrueConditionVarImpl _swap_condition;
+  AtomicAdjust::Integer _last_wait_frame = 0;
+
 protected:
   virtual void query_gl_version();
   virtual void *do_get_extension_func(const char *name);

+ 68 - 4
panda/src/cocoadisplay/cocoaGraphicsStateGuardian.mm

@@ -28,6 +28,21 @@
 #define NSAppKitVersionNumber10_7 1138
 #endif
 
+/**
+ * Called whenever a display wants a frame.  The context argument contains the
+ * applicable CocoaGraphicsStateGuardian.
+ */
+static CVReturn
+display_link_cb(CVDisplayLinkRef link, const CVTimeStamp *now,
+                const CVTimeStamp* output_time, CVOptionFlags flags_in,
+                CVOptionFlags *flags_out, void *context) {
+  CocoaGraphicsStateGuardian *gsg = (CocoaGraphicsStateGuardian *)context;
+  gsg->_swap_lock.lock();
+  gsg->_swap_condition.notify();
+  gsg->_swap_lock.unlock();
+  return kCVReturnSuccess;
+}
+
 TypeHandle CocoaGraphicsStateGuardian::_type_handle;
 
 /**
@@ -36,7 +51,8 @@ TypeHandle CocoaGraphicsStateGuardian::_type_handle;
 CocoaGraphicsStateGuardian::
 CocoaGraphicsStateGuardian(GraphicsEngine *engine, GraphicsPipe *pipe,
                            CocoaGraphicsStateGuardian *share_with) :
-  GLGraphicsStateGuardian(engine, pipe)
+  GLGraphicsStateGuardian(engine, pipe),
+  _swap_condition(_swap_lock)
 {
   _share_context = nil;
   _context = nil;
@@ -52,12 +68,60 @@ CocoaGraphicsStateGuardian(GraphicsEngine *engine, GraphicsPipe *pipe,
  */
 CocoaGraphicsStateGuardian::
 ~CocoaGraphicsStateGuardian() {
+  if (_format != nil) {
+    [_format release];
+  }
+  if (_display_link != nil) {
+    CVDisplayLinkRelease(_display_link);
+    _display_link = nil;
+    _swap_lock.lock();
+    _swap_condition.notify();
+    _swap_lock.unlock();
+  }
   if (_context != nil) {
     [_context clearDrawable];
     [_context release];
   }
 }
 
+/**
+ * Creates a CVDisplayLink, which tells us when the display the window is on
+ * will want a frame.
+ */
+bool CocoaGraphicsStateGuardian::
+setup_vsync() {
+  if (_display_link != nil) {
+    // Already set up.
+    return true;
+  }
+
+  CVReturn result = CVDisplayLinkCreateWithActiveCGDisplays(&_display_link);
+  if (result != kCVReturnSuccess) {
+    cocoadisplay_cat.error() << "Failed to create CVDisplayLink.\n";
+    return false;
+  }
+
+  result = CVDisplayLinkSetCurrentCGDisplayFromOpenGLContext(_display_link, (CGLContextObj)[_context CGLContextObj], (CGLPixelFormatObj)[_format CGLPixelFormatObj]);
+  if (result != kCVReturnSuccess) {
+    cocoadisplay_cat.error() << "Failed to set CVDisplayLink's current display.\n";
+    return false;
+  }
+
+  result = CVDisplayLinkSetOutputCallback(_display_link, &display_link_cb, this);
+  if (result != kCVReturnSuccess) {
+    cocoadisplay_cat.error() << "Failed to set CVDisplayLink output callback.\n";
+    return false;
+  }
+
+  result = CVDisplayLinkStart(_display_link);
+  if (result != kCVReturnSuccess) {
+    cocoadisplay_cat.error() << "Failed to start the CVDisplayLink.\n";
+    return false;
+  }
+
+  return true;
+}
+
 /**
  * Gets the FrameBufferProperties to match the indicated config.
  */
@@ -262,15 +326,15 @@ choose_pixel_format(const FrameBufferProperties &properties,
   // TODO: print out renderer
 
   _context = [[NSOpenGLContext alloc] initWithFormat:format shareContext:_share_context];
-  [format release];
+  _format = format;
   if (_context == nil) {
     cocoadisplay_cat.error() <<
       "Failed to create OpenGL context!\n";
     return;
   }
 
-  // Set vsync setting on the context
-  GLint swap = sync_video ? 1 : 0;
+  // Disable vsync via the built-in mechanism, which doesn't work on Mojave
+  GLint swap = 0;
   [_context setValues:&swap forParameter:NSOpenGLCPSwapInterval];
 
   cocoadisplay_cat.debug()

+ 4 - 0
panda/src/cocoadisplay/cocoaGraphicsWindow.h

@@ -23,6 +23,8 @@
 #import <AppKit/NSView.h>
 #import <AppKit/NSWindow.h>
 
+#import <CoreVideo/CoreVideo.h>
+
 /**
  * An interface to the Cocoa system for managing OpenGL windows under Mac OS
  * X.
@@ -89,8 +91,10 @@ private:
   NSUInteger _modifier_keys;
   UInt32 _dead_key_state;
   CGDirectDisplayID _display;
+  PT(GraphicsWindowInputDevice) _input;
   bool _mouse_hidden;
   bool _context_needs_update;
+  bool _vsync_enabled = false;
 
 #if __MAC_OS_X_VERSION_MAX_ALLOWED >= 1060
   CGDisplayModeRef _fullscreen_mode;

Alguns arquivos não foram mostrados porque muitos arquivos mudaram nesse diff