Browse Source

Merge branch 'release/1.9.x'

Conflicts:
	direct/src/p3d/Packager.py
	direct/src/p3d/ppackage.py
	makepanda/makepandacore.py
rdb 10 years ago
parent
commit
0a9f9887f9

+ 4 - 2
direct/src/actor/Actor.py

@@ -1532,8 +1532,10 @@ class Actor(DirectObject, NodePath):
 
     # actions
     def animPanel(self):
-        from direct.showbase import TkGlobal
-        from direct.tkpanels import AnimPanel
+        # Don't use a regular import, to prevent ModuleFinder from picking
+        # it up as a dependency when building a .p3d package.
+        import importlib
+        AnimPanel = importlib.import_module('direct.tkpanels.AnimPanel')
         return AnimPanel.AnimPanel(self)
 
     def stop(self, animName=None, partName=None):

+ 6 - 3
direct/src/directnotify/DirectNotify.py

@@ -111,10 +111,13 @@ class DirectNotify:
             category.setWarning(1)
             category.setInfo(1)
             category.setDebug(1)
-            
+
     def popupControls(self, tl = None):
-        from direct.tkpanels import NotifyPanel
+        # Don't use a regular import, to prevent ModuleFinder from picking
+        # it up as a dependency when building a .p3d package.
+        import importlib
+        NotifyPanel = importlib.import_module('direct.tkpanels.NotifyPanel')
         NotifyPanel.NotifyPanel(self, tl)
-        
+
     def giveNotify(self,cls):
         cls.notify = self.newCategory(cls.__name__)

+ 20 - 16
direct/src/extensions_native/CInterval_extensions.py

@@ -60,13 +60,17 @@ def popupControls(self, tl = None):
         """
         Popup control panel for interval.
         """
-        from direct.showbase.TkGlobal import Toplevel, Frame, Button, LEFT, X, Pmw
         import math
-        from direct.tkwidgets import EntryScale
+        # Don't use a regular import, to prevent ModuleFinder from picking
+        # it up as a dependency when building a .p3d package.
+        import importlib
+        EntryScale = importlib.import_module('direct.tkwidgets.EntryScale')
+        Tkinter = importlib.import_module('Tkinter')
+
         if tl == None:
-            tl = Toplevel()
+            tl = Tkinter.Toplevel()
             tl.title('Interval Controls')
-        outerFrame = Frame(tl)
+        outerFrame = Tkinter.Frame(tl)
         def entryScaleCommand(t, s=self):
             s.setT(t)
             s.pause()
@@ -75,8 +79,8 @@ def popupControls(self, tl = None):
             min = 0, max = math.floor(self.getDuration() * 100) / 100,
             command = entryScaleCommand)
         es.set(self.getT(), fCommand = 0)
-        es.pack(expand = 1, fill = X)
-        bf = Frame(outerFrame)
+        es.pack(expand = 1, fill = Tkinter.X)
+        bf = Tkinter.Frame(outerFrame)
         # Jump to start and end
         def toStart(s=self, es=es):
             s.setT(0.0)
@@ -84,23 +88,23 @@ def popupControls(self, tl = None):
         def toEnd(s=self):
             s.setT(s.getDuration())
             s.pause()
-        jumpToStart = Button(bf, text = '<<', command = toStart)
+        jumpToStart = Tkinter.Button(bf, text = '<<', command = toStart)
         # Stop/play buttons
         def doPlay(s=self, es=es):
             s.resume(es.get())
 
-        stop = Button(bf, text = 'Stop',
+        stop = Tkinter.Button(bf, text = 'Stop',
                       command = lambda s=self: s.pause())
-        play = Button(
+        play = Tkinter.Button(
             bf, text = 'Play',
             command = doPlay)
-        jumpToEnd = Button(bf, text = '>>', command = toEnd)
-        jumpToStart.pack(side = LEFT, expand = 1, fill = X)
-        play.pack(side = LEFT, expand = 1, fill = X)
-        stop.pack(side = LEFT, expand = 1, fill = X)
-        jumpToEnd.pack(side = LEFT, expand = 1, fill = X)
-        bf.pack(expand = 1, fill = X)
-        outerFrame.pack(expand = 1, fill = X)
+        jumpToEnd = Tkinter.Button(bf, text = '>>', command = toEnd)
+        jumpToStart.pack(side = Tkinter.LEFT, expand = 1, fill = Tkinter.X)
+        play.pack(side = Tkinter.LEFT, expand = 1, fill = Tkinter.X)
+        stop.pack(side = Tkinter.LEFT, expand = 1, fill = Tkinter.X)
+        jumpToEnd.pack(side = Tkinter.LEFT, expand = 1, fill = Tkinter.X)
+        bf.pack(expand = 1, fill = Tkinter.X)
+        outerFrame.pack(expand = 1, fill = Tkinter.X)
         # Add function to update slider during setT calls
         def update(t, es=es):
             es.set(t, fCommand = 0)

+ 12 - 3
direct/src/extensions_native/NodePath_extensions.py

@@ -407,7 +407,10 @@ del iPosHprScale
 #####################################################################
 def place(self):
         base.startDirect(fWantTk = 1)
-        from direct.tkpanels import Placer
+        # Don't use a regular import, to prevent ModuleFinder from picking
+        # it up as a dependency when building a .p3d package.
+        import importlib
+        Placer = importlib.import_module('direct.tkpanels.Placer')
         return Placer.place(self)
 
 Dtool_funcToMethod(place, NodePath)
@@ -415,7 +418,10 @@ del place
 #####################################################################
 def explore(self):
         base.startDirect(fWantTk = 1)
-        from direct.tkwidgets import SceneGraphExplorer
+        # Don't use a regular import, to prevent ModuleFinder from picking
+        # it up as a dependency when building a .p3d package.
+        import importlib
+        SceneGraphExplorer = importlib.import_module('direct.tkwidgets.SceneGraphExplorer')
         return SceneGraphExplorer.explore(self)
 
 Dtool_funcToMethod(explore, NodePath)
@@ -423,7 +429,10 @@ del explore
 #####################################################################
 def rgbPanel(self, cb = None):
         base.startTk()
-        from direct.tkwidgets import Slider
+        # Don't use a regular import, to prevent ModuleFinder from picking
+        # it up as a dependency when building a .p3d package.
+        import importlib
+        Slider = importlib.import_module('direct.tkwidgets.Slider')
         return Slider.rgbPanel(self, cb)
 
 Dtool_funcToMethod(rgbPanel, NodePath)

+ 4 - 1
direct/src/fsm/ClassicFSM.py

@@ -369,7 +369,10 @@ class ClassicFSM(DirectObject):
             return 0
 
     def view(self):
-        from direct.tkpanels import FSMInspector
+        # Don't use a regular import, to prevent ModuleFinder from picking
+        # it up as a dependency when building a .p3d package.
+        import importlib
+        FSMInspector = importlib.import_module('direct.tkpanels.FSMInspector')
         FSMInspector.FSMInspector(self)
 
     def isInternalStateInFlux(self):

+ 20 - 20
direct/src/interval/Interval.py

@@ -444,16 +444,16 @@ class Interval(DirectObject):
         """
         Popup control panel for interval.
         """
-        from direct.showbase import TkGlobal
-        import math
-        # I moved this here because Toontown does not ship Tk
-        from Tkinter import Toplevel, Frame, Button, LEFT, X
-        import Pmw
-        from direct.tkwidgets import EntryScale
+        # Don't use a regular import, to prevent ModuleFinder from picking
+        # it up as a dependency when building a .p3d package.
+        import importlib
+        EntryScale = importlib.import_module('direct.tkwidgets.EntryScale')
+        Tkinter = importlib.import_module('Tkinter')
+
         if tl == None:
-            tl = Toplevel()
+            tl = Tkinter.Toplevel()
             tl.title('Interval Controls')
-        outerFrame = Frame(tl)
+        outerFrame = Tkinter.Frame(tl)
         def entryScaleCommand(t, s=self):
             s.setT(t)
             s.pause()
@@ -462,8 +462,8 @@ class Interval(DirectObject):
             min = 0, max = math.floor(self.getDuration() * 100) / 100,
             command = entryScaleCommand)
         es.set(self.getT(), fCommand = 0)
-        es.pack(expand = 1, fill = X)
-        bf = Frame(outerFrame)
+        es.pack(expand = 1, fill = Tkinter.X)
+        bf = Tkinter.Frame(outerFrame)
         # Jump to start and end
         def toStart(s=self, es=es):
             s.clearToInitial()
@@ -473,23 +473,23 @@ class Interval(DirectObject):
             s.setT(s.getDuration())
             es.set(s.getDuration(), fCommand = 0)
             s.pause()
-        jumpToStart = Button(bf, text = '<<', command = toStart)
+        jumpToStart = Tkinter.Button(bf, text = '<<', command = toStart)
         # Stop/play buttons
         def doPlay(s=self, es=es):
             s.resume(es.get())
 
-        stop = Button(bf, text = 'Stop',
+        stop = Tkinter.Button(bf, text = 'Stop',
                       command = lambda s=self: s.pause())
-        play = Button(
+        play = Tkinter.Button(
             bf, text = 'Play',
             command = doPlay)
-        jumpToEnd = Button(bf, text = '>>', command = toEnd)
-        jumpToStart.pack(side = LEFT, expand = 1, fill = X)
-        play.pack(side = LEFT, expand = 1, fill = X)
-        stop.pack(side = LEFT, expand = 1, fill = X)
-        jumpToEnd.pack(side = LEFT, expand = 1, fill = X)
-        bf.pack(expand = 1, fill = X)
-        outerFrame.pack(expand = 1, fill = X)
+        jumpToEnd = Tkinter.Button(bf, text = '>>', command = toEnd)
+        jumpToStart.pack(side = Tkinter.LEFT, expand = 1, fill = Tkinter.X)
+        play.pack(side = Tkinter.LEFT, expand = 1, fill = Tkinter.X)
+        stop.pack(side = Tkinter.LEFT, expand = 1, fill = Tkinter.X)
+        jumpToEnd.pack(side = Tkinter.LEFT, expand = 1, fill = Tkinter.X)
+        bf.pack(expand = 1, fill = Tkinter.X)
+        outerFrame.pack(expand = 1, fill = Tkinter.X)
         # Add function to update slider during setT calls
         def update(t, es=es):
             es.set(t, fCommand = 0)

+ 1 - 40
direct/src/p3d/AppRunner.py

@@ -424,47 +424,8 @@ class AppRunner(DirectObject):
         it downloads a new version on-the-spot.  Returns true on
         success, false on failure. """
 
-        if fileSpec.quickVerify(pathname = localPathname):
-            # It's good, keep it.
-            return True
-
         assert self.http
-
-        # It's stale, get a new one.
-        doc = None
-        if self.superMirrorUrl:
-            # Use the "super mirror" first.
-            url = core.URLSpec(self.superMirrorUrl + fileSpec.filename)
-            self.notify.info("Freshening %s" % (url))
-            doc = self.http.getDocument(url)
-
-        if not doc or not doc.isValid():
-            # Failing the super mirror, contact the actual host.
-            url = core.URLSpec(host.hostUrlPrefix + fileSpec.filename)
-            self.notify.info("Freshening %s" % (url))
-            doc = self.http.getDocument(url)
-            if not doc.isValid():
-                return False
-
-        file = Filename.temporary('', 'p3d_')
-        if not doc.downloadToFile(file):
-            # Failed to download.
-            file.unlink()
-            return False
-
-        # Successfully downloaded!
-        localPathname.makeDir()
-        if not file.renameTo(localPathname):
-            # Couldn't move it into place.
-            file.unlink()
-            return False
-
-        if not fileSpec.fullVerify(pathname = localPathname, notify = self.notify):
-            # No good after download.
-            self.notify.info("%s is still no good after downloading." % (url))
-            return False
-
-        return True
+        return host.freshenFile(self.http, fileSpec, localPathname)
 
     def scanInstalledPackages(self):
         """ Scans the hosts and packages already installed locally on

+ 46 - 2
direct/src/p3d/HostInfo.py

@@ -40,8 +40,6 @@ class HostInfo:
         Note that perPlatform is also restricted by the individual
         package's specification.  """
 
-        assert appRunner or rootDir or hostDir
-
         self.__setHostUrl(hostUrl)
         self.appRunner = appRunner
         self.rootDir = rootDir
@@ -112,6 +110,52 @@ class HostInfo:
             # https-protected hostUrl, it will be the cleartext channel.
             self.downloadUrlPrefix = self.hostUrlPrefix
 
+    def freshenFile(self, http, fileSpec, localPathname):
+        """ Ensures that the localPathname is the most current version
+        of the file defined by fileSpec, as offered by host.  If not,
+        it downloads a new version on-the-spot.  Returns true on
+        success, false on failure. """
+
+        if fileSpec.quickVerify(pathname = localPathname):
+            # It's good, keep it.
+            return True
+
+        # It's stale, get a new one.
+        doc = None
+        if self.appRunner and self.appRunner.superMirrorUrl:
+            # Use the "super mirror" first.
+            url = core.URLSpec(self.appRunner.superMirrorUrl + fileSpec.filename)
+            self.notify.info("Freshening %s" % (url))
+            doc = http.getDocument(url)
+
+        if not doc or not doc.isValid():
+            # Failing the super mirror, contact the actual host.
+            url = core.URLSpec(self.hostUrlPrefix + fileSpec.filename)
+            self.notify.info("Freshening %s" % (url))
+            doc = http.getDocument(url)
+            if not doc.isValid():
+                return False
+
+        file = Filename.temporary('', 'p3d_')
+        if not doc.downloadToFile(file):
+            # Failed to download.
+            file.unlink()
+            return False
+
+        # Successfully downloaded!
+        localPathname.makeDir()
+        if not file.renameTo(localPathname):
+            # Couldn't move it into place.
+            file.unlink()
+            return False
+
+        if not fileSpec.fullVerify(pathname = localPathname, notify = self.notify):
+            # No good after download.
+            self.notify.info("%s is still no good after downloading." % (url))
+            return False
+
+        return True
+
     def downloadContentsFile(self, http, redownload = False,
                              hashVal = None):
         """ Downloads the contents.xml file for this particular host,

+ 45 - 17
direct/src/p3d/Packager.py

@@ -13,12 +13,12 @@ import os
 import glob
 import string
 import types
-import getpass
 import struct
 import subprocess
 import copy
 from direct.p3d.FileSpec import FileSpec
 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
@@ -378,7 +378,8 @@ class Packager:
             # This records the current list of modules we have added so
             # far.
             self.freezer = FreezeTool.Freezer(platform = self.packager.platform)
-            
+            self.freezer.storePythonSource = self.packager.storePythonSource
+
             # Map of extensions to files to number (ignored by dir)
             self.ignoredDirFiles = {}
 
@@ -1075,7 +1076,7 @@ class Packager:
             fpath.append(Filename("/Library/Frameworks"))
             fpath.append(Filename("/System/Library/Frameworks"))
             fpath.append(Filename("/Developer/Library/Frameworks"))
-            fpath.append(Filename("/Users/%s" % getpass.getuser(), "Library/Frameworks"))
+            fpath.append(Filename(os.path.expanduser("~"), "Library/Frameworks"))
             if "HOME" in os.environ:
                 fpath.append(Filename(os.environ["HOME"], "Library/Frameworks"))
             ffilename = Filename(library.split('.framework/', 1)[0].split('/')[-1] + '.framework')
@@ -2233,6 +2234,11 @@ class Packager:
         self.host = PandaSystem.getPackageHostUrl()
         self.addHost(self.host)
 
+        # This will be used when we're not compiling in the packaged
+        # environment.
+        self.__hostInfos = {}
+        self.http = HTTPClient.getGlobalPtr()
+
         # The maximum amount of time a client should cache the
         # contents.xml before re-querying the server, in seconds.
         self.maxAge = 0
@@ -2318,6 +2324,10 @@ class Packager:
         # any applications.
         self.allowPythonDev = False
 
+        # Set this flag to store the original Python source files,
+        # without compiling them to .pyc or .pyo.
+        self.storePythonSource = False
+
         # Fill this with a list of (certificate, chain, pkey,
         # password) tuples to automatically sign each p3d file
         # generated.
@@ -3028,17 +3038,10 @@ class Packager:
 
     def __findPackageOnHost(self, packageName, platform, version, hostUrl, requires = None):
         appRunner = AppRunnerGlobal.appRunner
-        if not appRunner:
-            # We don't download import files from a host unless we're
-            # running in a packaged environment ourselves.  It would
-            # be possible to do this, but a fair bit of work for not
-            # much gain--this is meant to be run in a packaged
-            # environment.
-            return None
 
         # Make sure we have a fresh version of the contents file.
-        host = appRunner.getHost(hostUrl)
-        if not host.downloadContentsFile(appRunner.http):
+        host = self.__getHostInfo(hostUrl)
+        if not host.downloadContentsFile(self.http):
             return None
 
         packageInfos = []
@@ -3063,23 +3066,48 @@ class Packager:
 
             # Now we've retrieved a PackageInfo.  Get the import desc file
             # from it.
-            filename = Filename(host.hostDir, 'imports/' + packageInfo.importDescFile.basename)
-            if not appRunner.freshenFile(host, packageInfo.importDescFile, filename):
+            if host.hostDir:
+                filename = Filename(host.hostDir, 'imports/' + packageInfo.importDescFile.basename)
+            else:
+                # We're not running in the packaged environment, so download
+                # to a temporary file instead of the host directory.
+                filename = Filename.temporary('', 'import_' + packageInfo.importDescFile.basename, '.xml')
+
+            if not host.freshenFile(self.http, packageInfo.importDescFile, filename):
                 self.notify.error("Couldn't download import file.")
                 continue
 
             # Now that we have the import desc file, use it to load one of
             # our Package objects.
             package = self.Package('', self)
-            if not package.readImportDescFile(filename):
-                continue
+            success = package.readImportDescFile(filename)
+
+            if not host.hostDir:
+                # Don't forget to delete the temporary file we created.
+                filename.unlink()
 
-            if self.__packageIsValid(package, requires, platform):
+            if success and self.__packageIsValid(package, requires, platform):
                 return package
 
         # Couldn't find a suitable package.
         return None
 
+    def __getHostInfo(self, hostUrl = None):
+        """ This shadows appRunner.getHost(), for the purpose of running
+        outside the packaged environment. """
+
+        if not hostUrl:
+            hostUrl = PandaSystem.getPackageHostUrl()
+
+        if AppRunnerGlobal.appRunner:
+            return AppRunnerGlobal.appRunner.getHost(hostUrl)
+
+        host = self.__hostInfos.get(hostUrl, None)
+        if not host:
+            host = HostInfo(hostUrl)
+            self.__hostInfos[hostUrl] = host
+        return host
+
     def __sortImportPackages(self, packages):
         """ Given a list of Packages read from *.import.xml filenames,
         sorts them in reverse order by version, so that the

+ 9 - 1
direct/src/p3d/ppackage.py

@@ -83,6 +83,10 @@ Options:
      initially, but should not be set on an application intended for
      deployment.
 
+  -N
+     If this option is set, Packager will not try to compile any Python
+     files to .pyc or .pyo, instead storing the original source files.
+
   -u
      On the Mac OSX platform, this means that Panda was built with
      universal binaries, and the package should be built that way as
@@ -152,6 +156,7 @@ buildPatches = False
 installSearch = []
 signParams = []
 allowPythonDev = False
+storePythonSource = False
 universalBinaries = False
 systemRoot = None
 ignoreSetHost = False
@@ -160,7 +165,7 @@ p3dSuffix = ''
 platforms = []
 
 try:
-    opts, args = getopt.getopt(sys.argv[1:], 'i:ps:S:DuP:R:Ha:hv')
+    opts, args = getopt.getopt(sys.argv[1:], 'i:ps:S:DNuP:R:Ha:hv')
 except getopt.error as msg:
     usage(1, msg)
 
@@ -182,6 +187,8 @@ for opt, arg in opts:
                            Filename.fromOsSpecific(password)))
     elif opt == '-D':
         allowPythonDev = True
+    elif opt == '-N':
+        storePythonSource = True
     elif opt == '-u':
         universalBinaries = True
     elif opt == '-P':
@@ -236,6 +243,7 @@ for platform in platforms:
         packager.installSearch = [installDir] + packager.installSearch
     packager.signParams = signParams
     packager.allowPythonDev = allowPythonDev
+    packager.storePythonSource = storePythonSource
     packager.systemRoot = systemRoot
     packager.ignoreSetHost = ignoreSetHost
     packager.verbosePrint = verbosePrint

+ 4 - 1
direct/src/showbase/PythonUtil.py

@@ -325,7 +325,10 @@ def adjust(command = None, dim = 1, parent = None, **kw):
     10.0
     """
     # Make sure we enable Tk
-    from direct.tkwidgets import Valuator
+    # Don't use a regular import, to prevent ModuleFinder from picking
+    # it up as a dependency when building a .p3d package.
+    import importlib
+    Valuator = importlib.import_module('direct.tkwidgets.Valuator')
     # Set command if specified
     if command:
         kw['command'] = lambda x: apply(command, x)

+ 4 - 1
direct/src/showbase/ShowBaseGlobal.py

@@ -14,7 +14,10 @@ assert base
 directNotify.setDconfigLevels()
 
 def inspect(anObject):
-    from direct.tkpanels import Inspector
+    # Don't use a regular import, to prevent ModuleFinder from picking
+    # it up as a dependency when building a .p3d package.
+    import importlib
+    Inspector = importlib.import_module('direct.tkpanels.Inspector')
     return Inspector.inspect(anObject)
 
 import __builtin__

+ 1 - 0
direct/src/showutil/FreezeTool.py

@@ -1143,6 +1143,7 @@ class Freezer:
         elif getattr(module, '__file__', None):
             sourceFilename = Filename.fromOsSpecific(module.__file__)
             sourceFilename.setExtension("py")
+            sourceFilename.setText()
 
         if self.storePythonSource:
             if sourceFilename and sourceFilename.exists():

+ 4 - 1
direct/src/task/Task.py

@@ -582,7 +582,10 @@ class TaskManager:
         return numFound
 
     def popupControls(self):
-        from direct.tkpanels import TaskManagerPanel
+        # Don't use a regular import, to prevent ModuleFinder from picking
+        # it up as a dependency when building a .p3d package.
+        import importlib
+        TaskManagerPanel = importlib.import_module('direct.tkpanels.TaskManagerPanel')
         return TaskManagerPanel.TaskManagerPanel(self)
 
     def getProfileSession(self, name=None):

+ 5 - 0
dtool/src/interrogate/interfaceMakerPythonNative.cxx

@@ -4179,6 +4179,11 @@ bool RemapCompareLess(FunctionRemap *in1, FunctionRemap *in2) {
   assert(in1 != NULL);
   assert(in2 != NULL);
 
+  if (in1->_const_method != in2->_const_method) {
+    // Non-const methods should come first.
+    return in2->_const_method;
+  }
+
   if (in1->_parameters.size() != in2->_parameters.size()) {
     return (in1->_parameters.size() > in2->_parameters.size());
   }

+ 40 - 18
makepanda/makepanda.py

@@ -1856,6 +1856,11 @@ def Package(target, inputs, opts):
 
     command += "direct/src/p3d/ppackage.py"
 
+    if not RTDIST:
+        # Don't compile Python sources, because we might not running in the same
+        # Python version as the selected host.
+        command += " -N"
+
     if GetTarget() == "darwin":
         if SDK.get("MACOSX"):
             command += " -R \"%s\"" % SDK["MACOSX"]
@@ -1868,8 +1873,31 @@ def Package(target, inputs, opts):
     command += " -i \"" + GetOutputDir() + "/stage\""
     if (P3DSUFFIX):
         command += ' -a "' + P3DSUFFIX + '"'
+
     command += " " + inputs[0]
-    oscmd(command)
+
+    if GetOrigExt(target) == '.p3d':
+        # Build a specific .p3d file.
+        basename = os.path.basename(os.path.splitext(target)[0])
+        command += " " + basename
+        oscmd(command)
+
+        if GetTarget() == 'windows':
+            # Make an .exe that calls this .p3d.
+            objfile = FindLocation('p3dWrapper_' + basename + '.obj', [])
+            CompileCxx(objfile, 'direct/src/p3d/p3dWrapper.c', [])
+
+            exefile = FindLocation(basename + '.exe', [])
+            CompileLink(exefile, [objfile], ['ADVAPI'])
+
+        # Move it to the bin directory.
+        os.rename(GetOutputDir() + '/stage/' + basename + P3DSUFFIX + '.p3d', target)
+
+        if sys.platform != 'win32':
+            oscmd('chmod +x ' + BracketNameWithQuotes(target))
+    else:
+        # This is presumably a package or set of packages.
+        oscmd(command)
 
 ##########################################################################################
 #
@@ -1970,7 +1998,10 @@ def CompileAnything(target, inputs, opts, progress = None):
         ProgressOutput(progress, "Compiling MIDL file", infile)
         return CompileMIDL(target, infile, opts)
     elif (infile.endswith(".pdef")):
-        ProgressOutput(progress, "Building package from pdef file", infile)
+        if origsuffix == '.p3d':
+            ProgressOutput(progress, "Building package", target)
+        else:
+            ProgressOutput(progress, "Building package from pdef file", infile)
         return Package(target, inputs, opts)
     elif origsuffix in SUFFIX_LIB:
         ProgressOutput(progress, "Linking static library", target)
@@ -6234,26 +6265,17 @@ if (RTDIST):
   TargetAdd('_thirdparty', opts=OPTS, input='thirdparty.pdef')
 
 #
-# Distribute prebuilt .p3d files as executable.
+# If we have a host URL and distributor, we can make .p3d deployment tools.
 #
 
-if (PkgSkip("DIRECT")==0 and not RUNTIME and not RTDIST):
-  if GetTarget() == 'windows':
+if not PkgSkip("DIRECT") and not PkgSkip("DEPLOYTOOLS") and not RUNTIME and not RTDIST and HOST_URL and DISTRIBUTOR:
     OPTS=['DIR:direct/src/p3d']
-    TargetAdd('p3dWrapper.obj', opts=OPTS, input='p3dWrapper.c')
-    TargetAdd('p3dWrapper.exe', input='p3dWrapper.obj')
-    TargetAdd('p3dWrapper.exe', opts=["ADVAPI"])
 
-  for g in glob.glob("direct/src/p3d/*.p3d"):
-    base = os.path.basename(g)
-    base = base.split(".", 1)[0]
-
-    if GetTarget() == 'windows':
-      TargetAdd(base+".exe", input='p3dWrapper.exe')
-      CopyFile(GetOutputDir()+"/bin/"+base+".p3d", g)
-    else:
-      CopyFile(GetOutputDir()+"/bin/"+base, g)
-      oscmd("chmod +x "+GetOutputDir()+"/bin/"+base)
+    TargetAdd('packp3d.p3d', opts=OPTS, input='panda3d.pdef')
+    TargetAdd('pdeploy.p3d', opts=OPTS, input='panda3d.pdef')
+    TargetAdd('pmerge.p3d', opts=OPTS, input='panda3d.pdef')
+    TargetAdd('ppackage.p3d', opts=OPTS, input='panda3d.pdef')
+    TargetAdd('ppatcher.p3d', opts=OPTS, input='panda3d.pdef')
 
 ##########################################################################################
 #

+ 4 - 1
makepanda/makepandacore.py

@@ -1881,7 +1881,7 @@ def SdkLocatePython(prefer_thirdparty_python=False):
             sdkdir += "-x64"
 
         SDK["PYTHON"] = sdkdir
-        SDK["PYTHONEXEC"] = SDK["PYTHON"].replace('/', '\\') + "\\python"
+        SDK["PYTHONEXEC"] = SDK["PYTHON"].replace('\\', '/') + "/python"
         if (GetOptimize() <= 2):
             SDK["PYTHONEXEC"] += "_d.exe"
         else:
@@ -2824,6 +2824,7 @@ def CalcLocation(fn, ipath):
         if (fn.endswith(".dle")):   return OUTPUTDIR+"/plugins/"+fn[:-4]+dllext+".dle"
         if (fn.endswith(".plugin")):return OUTPUTDIR+"/plugins/"+fn[:-7]+dllext+".dll"
         if (fn.endswith(".exe")):   return OUTPUTDIR+"/bin/"+fn
+        if (fn.endswith(".p3d")):   return OUTPUTDIR+"/bin/"+fn
         if (fn.endswith(".lib")):   return OUTPUTDIR+"/lib/"+fn[:-4]+dllext+".lib"
         if (fn.endswith(".ilb")):   return OUTPUTDIR+"/tmp/"+fn[:-4]+dllext+".lib"
     elif (target == 'darwin'):
@@ -2835,6 +2836,7 @@ def CalcLocation(fn, ipath):
         if (fn.endswith(".pyd")):   return OUTPUTDIR+"/panda3d/"+fn[:-4]+".so"
         if (fn.endswith(".mll")):   return OUTPUTDIR+"/plugins/"+fn
         if (fn.endswith(".exe")):   return OUTPUTDIR+"/bin/"+fn[:-4]
+        if (fn.endswith(".p3d")):   return OUTPUTDIR+"/bin/"+fn[:-4]
         if (fn.endswith(".lib")):   return OUTPUTDIR+"/lib/"+fn[:-4]+".a"
         if (fn.endswith(".ilb")):   return OUTPUTDIR+"/tmp/"+fn[:-4]+".a"
         if (fn.endswith(".rsrc")):  return OUTPUTDIR+"/tmp/"+fn
@@ -2857,6 +2859,7 @@ def CalcLocation(fn, ipath):
         if (fn.endswith(".mll")):   return OUTPUTDIR+"/plugins/"+fn
         if (fn.endswith(".plugin")):return OUTPUTDIR+"/plugins/"+fn[:-7]+dllext+".so"
         if (fn.endswith(".exe")):   return OUTPUTDIR+"/bin/"+fn[:-4]
+        if (fn.endswith(".p3d")):   return OUTPUTDIR+"/bin/"+fn[:-4]
         if (fn.endswith(".lib")):   return OUTPUTDIR+"/lib/"+fn[:-4]+".a"
         if (fn.endswith(".ilb")):   return OUTPUTDIR+"/tmp/"+fn[:-4]+".a"
     if (fn.endswith(".dat")):   return OUTPUTDIR+"/tmp/"+fn