如何使用setuptools Windows安装程序创建开始菜单快捷方式
我想为我的Python Windows安装包创建一个开始菜单或桌面快捷方式。我正在尝试按照这个链接上的说明来做。
这是我的脚本:
import sys
from os.path import dirname, join, expanduser
pyw_executable = sys.executable.replace('python.exe','pythonw.exe')
script_file = join(dirname(pyw_executable), 'Scripts', 'tklsystem-script.py')
w_dir = expanduser(join('~','lsf_files'))
print(sys.argv)
if sys.argv[1] == '-install':
print('Creating Shortcut')
create_shortcut(
target=pyw_executable,
description='A program to work with L-System Equations',
filename='L-System Tool',
arguments=script_file,
workdir=wdir
)
我还在脚本设置选项中指定了这个脚本,就像上面提到的文档所说的那样。
这是我用来创建安装程序的命令:
python setup.py bdist_wininst --install-script tklsystem-post-install.py
在我使用创建的Windows安装程序安装我的包后,我找不到我的快捷方式在哪里,也无法确认我的脚本是否运行了。
我该如何让setuptools生成的Windows安装程序创建桌面或开始菜单的快捷方式呢?
4 个回答
更新:如果客户端机器上安装了 pywin32
,我们会先尝试在同一个进程中创建,这样会更干净一些。
这里还有另一种方法。这种方法假设你的包叫做 myapp
,这个包也会变成你想要创建快捷方式的可执行文件。你可以用自己的包名和快捷方式文本来替换。
这个方法使用了Windows脚本宿主的COM类,如果可能的话会在同一个进程中运行,如果不行的话就会在Powershell命令行中作为子进程运行。已经在Python 3.6及以上版本上测试过。
from setuptools import setup
from setuptools.command.install import install
import platform, sys, os, site
from os import path, environ
def create_shortcut_under(root, exepath):
# Root is an env variable name -
# either ALLUSERSPROFILE for the all users' Start menu,
# or APPDATA for the current user specific one
profile = environ[root]
linkpath = path.join(profile, "Microsoft", "Windows", "Start Menu", "Programs", "My Python app.lnk")
try:
from win32com.client import Dispatch
from pywintypes import com_error
try:
sh = Dispatch('WScript.Shell')
link = sh.CreateShortcut(linkpath)
link.TargetPath = exepath
link.Save()
return True
except com_error:
return False
except ImportError:
import subprocess
s = "$s=(New-Object -COM WScript.Shell).CreateShortcut('" + linkpath + "');$s.TargetPath='" + exepath + "';$s.Save()"
return subprocess.call(['powershell', s], stdout = subprocess.DEVNULL, stderr = subprocess.DEVNULL) == 0
def create_shortcut(inst):
try:
exepath = path.join(path.dirname(sys.executable), "Scripts", "myapp.exe")
if not path.exists(exepath):
# Support for "pip install --user"
exepath = path.join(path.dirname(site.getusersitepackages()), "Scripts", "myapp.exe")
# If can't modify the global menu, fall back to the
# current user's one
if not create_shortcut_under('ALLUSERSPROFILE', exepath):
create_shortcut_under('APPDATA', exepath)
except:
pass
class my_install(install):
def run(self):
install.run(self)
if platform.system() == 'Windows':
create_shortcut(self)
#...
setup(
#...
cmdclass={'install': my_install},
entry_points={"gui_scripts": ["myapp = myapp.__main__:main"]},
在Windows上使用Python 3.6.5的32位版本时,setuptools
确实可以用来实现这个功能。不过根据接受的答案,通过反复尝试,我发现了一些可能导致你的脚本无法正常工作的原因。
create_shortcut
这个函数不接受关键字参数,只能用位置参数,所以你在代码中的用法是错误的。- 你必须为Windows添加一个
.lnk
后缀,这样系统才能识别这个快捷方式。 - 我发现
sys.executable
返回的是安装程序的可执行文件名,而不是Python的可执行文件名。 - 正如提到的,你无法看到
stdout
或stderr
的输出,所以你可能需要把日志记录到一个文本文件中。我建议你也把sys.stdout
和sys.stderr
重定向到这个日志文件。 - (可能不太相关)正如在这个问题中提到的,
bdist_wininst
生成的版本字符串似乎有个bug。我使用了那里的一个答案中的十六进制编辑技巧来解决这个问题。答案中的位置可能不一样,你需要自己找到-32
。
完整的示例脚本:
import sys
import os
import datetime
global datadir
datadir = os.path.join(get_special_folder_path("CSIDL_APPDATA"), "mymodule")
def main(argv):
if "-install" in argv:
desktop = get_special_folder_path("CSIDL_DESKTOPDIRECTORY")
print("Desktop path: %s" % repr(desktop))
if not os.path.exists(datadir):
os.makedirs(datadir)
dir_created(datadir)
print("Created data directory: %s" % repr(datadir))
else:
print("Data directory already existed at %s" % repr(datadir))
shortcut = os.path.join(desktop, "MyModule.lnk")
if os.path.exists(shortcut):
print("Remove existing shortcut at %s" % repr(shortcut))
os.unlink(shortcut)
print("Creating shortcut at %s...\n" % shortcut)
create_shortcut(
r'C:\Python36\python.exe',
"MyModuleScript",
shortcut,
"",
datadir)
file_created(shortcut)
print("Successfull!")
elif "-remove" in sys.argv:
print("Removing...")
pass
if __name__ == "__main__":
logfile = r'C:\mymodule_install.log' # Fallback location
if os.path.exists(datadir):
logfile = os.path.join(datadir, "install.log")
elif os.environ.get("TEMP") and os.path.exists(os.environ.get("TEMP"),""):
logfile = os.path.join(os.environ.get("TEMP"), "mymodule_install.log")
with open(logfile, 'a+') as f:
f.write("Opened\r\n")
f.write("Ran %s %s at %s" % (sys.executable, " ".join(sys.argv), datetime.datetime.now().isoformat()))
sys.stdout = f
sys.stderr = f
try:
main(sys.argv)
except Exception as e:
raise
f.close()
sys.exit(0)
像其他人评论的那样,这些支持功能似乎根本不管用(至少在使用setuptools的时候)。经过一天的努力,我在各种资源中找到了一个方法,至少可以创建桌面快捷方式。我分享一下我的解决方案(基本上是我在这里和这里找到的代码的结合)。我还要补充一点,我的情况和yasar的稍微不同,因为我创建的是指向已安装包的快捷方式(也就是Python的Scripts目录中的一个.exe文件),而不是一个脚本。
简单来说,我在我的setup.py文件中添加了一个post_install函数,然后使用Windows的Python扩展来创建快捷方式。桌面文件夹的位置是从Windows注册表中读取的(还有其他方法可以获取这个位置,但如果桌面在非标准位置,这些方法可能不太可靠)。
#!/usr/bin/env python
import os
import sys
import sysconfig
if sys.platform == 'win32':
from win32com.client import Dispatch
import winreg
def get_reg(name,path):
# Read variable from Windows Registry
# From https://stackoverflow.com/a/35286642
try:
registry_key = winreg.OpenKey(winreg.HKEY_CURRENT_USER, path, 0,
winreg.KEY_READ)
value, regtype = winreg.QueryValueEx(registry_key, name)
winreg.CloseKey(registry_key)
return value
except WindowsError:
return None
def post_install():
# Creates a Desktop shortcut to the installed software
# Package name
packageName = 'mypackage'
# Scripts directory (location of launcher script)
scriptsDir = sysconfig.get_path('scripts')
# Target of shortcut
target = os.path.join(scriptsDir, packageName + '.exe')
# Name of link file
linkName = packageName + '.lnk'
# Read location of Windows desktop folder from registry
regName = 'Desktop'
regPath = r'Software\Microsoft\Windows\CurrentVersion\Explorer\User Shell Folders'
desktopFolder = os.path.normpath(get_reg(regName,regPath))
# Path to location of link file
pathLink = os.path.join(desktopFolder, linkName)
shell = Dispatch('WScript.Shell')
shortcut = shell.CreateShortCut(pathLink)
shortcut.Targetpath = target
shortcut.WorkingDirectory = scriptsDir
shortcut.IconLocation = target
shortcut.save()
setup(name='mypackage',
...,
...)
if sys.argv[1] == 'install' and sys.platform == 'win32':
post_install()
这里有一个完整的设置脚本链接,我在其中使用了这个方法:
https://github.com/KBNLresearch/iromlab/blob/master/setup.py
如果你想确认脚本是否在运行,可以把输出结果打印到一个文件里,而不是打印到控制台。因为在安装后脚本中打印到控制台的内容可能不会显示出来。
你可以试试这样做:
import sys
from os.path import expanduser, join
pyw_executable = join(sys.prefix, "pythonw.exe")
shortcut_filename = "L-System Toolsss.lnk"
working_dir = expanduser(join('~','lsf_files'))
script_path = join(sys.prefix, "Scripts", "tklsystem-script.py")
if sys.argv[1] == '-install':
# Log output to a file (for test)
f = open(r"C:\test.txt",'w')
print('Creating Shortcut', file=f)
# Get paths to the desktop and start menu
desktop_path = get_special_folder_path("CSIDL_COMMON_DESKTOPDIRECTORY")
startmenu_path = get_special_folder_path("CSIDL_COMMON_STARTMENU")
# Create shortcuts.
for path in [desktop_path, startmenu_path]:
create_shortcut(pyw_executable,
"A program to work with L-System Equations",
join(path, shortcut_filename),
script_path,
working_dir)