一.启动文件 odoo-bin.py
#!/usr/bin/env python3
# 这一行是一个shebang(用于指定脚本的解释器),它告诉操作系统用哪个解释器来执行这个脚本。
# 在这里,它指定使用/usr/bin/env下的python3解释器。
# 这样做的好处是,无论python3安装在哪个位置,这个脚本都可以找到并运行它。
__import__('os').environ['TZ'] = 'UTC'
# 动态地导入了os模块(读取或修改环境变量、操作文件和目录、获取系统信息等)
# 设置了环境变量 TZ(TimeZone)为 UTC(只改变了当前Python进程的环境变量副本。)
import odoo
# 导入odoo模块,这是Odoo系统的核心模块。
if __name__ == "__main__":
# Python的标准做法,用于检查脚本是否作为主程序运行(而不是被其他脚本导入)。
# 如果是,则执行下面的代码块。
odoo.cli.main()
# 调用odoo模块的cli子模块的main函数。
二.cli目录下 command.py文件中的main函数
1.获取系统启动参数
def main():
args = sys.argv[1:]
# 获取命令行参数(不包括脚本名称)
if len(args) > 1 and args[0].startswith('--addons-path=') and
not args[1].startswith("-"):
# 如果参数列表长度大于1,并且第一个参数以'--addons-path='开头,且第二个参数不是以'-'开头的选项
config._parse_config([args[0]])
# 使用odoo的内部函数_parse_config来解析addons路径
args = args[1:]
# 移除已处理的参数
command = "server"
# 默认启动命令的方式 为‘server’
2.获取模块信息并导入
# TODO: 找到一个方法来正确发现addons的子命令,而不需要导入所有的模块
if len(args) and not args[0].startswith("-"):
# 如果参数列表有内容,并且第一个参数不是以'-'开头的选项
logging.disable(logging.CRITICAL)
# 临时禁用日志记录,以避免在模块导入过程中记录不必要的日志消息
initialize_sys_path()
# 初始化系统路径,确保 Odoo 能够正确地找到其插件和升级脚本,并处理一些与旧代码兼容的别名重定向。
for module in get_modules():
# 遍历所有模块
if (Path(get_module_path(module)) / 'cli').is_dir():
# 如果模块的路径下有一个名为'cli'的目录
__import__('odoo.addons.' + module)
# 动态导入模块
logging.disable(logging.NOTSET)
# 重新启用日志记录
command = args[0]
# 将发现的子命令设置为命令
args = args[1:]
# 移除已处理的参数
3.根据命令,实例化服务并启动
if command in commands:
# 如果命令在预定义的命令字典中
o = commands[command]()
# 创建一个该命令的实例
o.run(args)
# 调用其run方法,传入剩余的参数
else:
sys.exit('Unknown command %r' % (command,))
# 如果命令不在命令字典中,则打印错误消息并退出程序
三、cli目录下的 server.py
1.run函数
def run(self, args): odoo.tools.config.parser.prog=f'{Path(sys.argv[0]).name} {self.name}'
# 设置 argparse.ArgumentParser 实例的 prog 属性
main(args)
1.sys.argv[0]:这是Python的命令行参数列表,其中 sys.argv[0] 是被执行的脚本的名称(通常是路径)。
2.Path(sys.argv[0]).name:使用 pathlib.Path 类的 name 属性来获取 sys.argv[0] 中的文件名(不包括路径)。这确保只显示脚本名而不是完整的路径。
3.self.name:这是 Server 类实例的 name 属性。在 Command 基类或其子类中,这个属性可能用于存储该命令的名称(例如,“server”)。
4.f'{Path(sys.argv[0]).name} {self.name}':这是一个f-string(格式化字符串字面量),它将脚本名和命令名组合成一个字符串。
2.main函数
1)检查权限、配置解析、报告配置以及CSV 字段大小限制
def main(args):
check_root_user()
# 检查是否以 root 用户身份运行服务器,如果用户的进程是root(在POSIX系统上),则发出警告
odoo.tools.config.parse_config(args)
# 解析命令行参数和配置文件
check_postgres_user()
# 检查与 PostgreSQL 数据库交互所需的权限和配置,如果配置的数据库用户是‘postgres’,退出
report_configuration()
# 用于初始化或配置数据库连接和日志记录
导入配置和日志记录模块
检查配置文件及记录其路径
检查升级路径,如果配置中存在upgrade_path参数,则记录其路径。
获取数据库连接参数,获取数据库的主机、端口和用户名,依次从Odoo的配置、操作系统的环境变量以及默认值“default”中获取。
记录数据库连接信息,记录日志,显示用于连接数据库的用户名、主机和端口
config = odoo.tools.config
# 将 odoo.tools.config 模块引用到一个名为 config 的变量中
csv.field_size_limit(500 * 1024 * 1024)
# 设置 CSV 字段的最大大小限制为 500 MiB。这是为了确保能够导入较大的文件,如包含图像的附件。
2)预加载数据库
preload = []
# 初始化预加载列表
if config['db_name']:
preload = config['db_name'].split(',')
# 解析数据库名列表,如果config['db_name']存在并且不为空,就将其按照逗号,分割成一个列表,每个元素都是一个数据库名。这个列表赋值给preload。
for db_name in preload:
# 遍历数据库名列表
try:
odoo.service.db._create_empty_database
(db_name)
# 创建一个空的Odoo数据库。
config['init']['base'] = True
# 设置config['init']['base'] = True,这通常用于指示后续的初始化步骤应该运行。
except ProgrammingError as err:
if err.pgcode == errorcodes.
INSUFFICIENT_PRIVILEGE:
# 表示当前数据库用户没有足够的权限来创建数据库
_logger.info(
"Could not determine if database %s exists, "
"skipping auto-creation: %s", db_name, err)
# 记录一个信息级别的日志,并跳过当前数据库的创建。这是为了避免在具有受限数据库访问权限的构建环境中报告不必要的警告。
else:
raise err
# 处理其他异常
except odoo.service.db.DatabaseExists:
pass
# 处理数据库已存在的异常,如果数据库已经存在(即odoo.service.db.DatabaseExists异常被触发),则代码会忽略这个异常,并继续处理下一个数据库名(如果有的话)。
3)翻译文件的导出和导入
if config["translate_out"]:
export_translation()
sys.exit(0)
# 如果在配置中设置了 translate_out,则调用 export_translation() 函数来导出翻译文件
if config["translate_in"]:
import_translation()
sys.exit(0)
# 如果在配置中设置了 translate_in,则调用 import_translation() 函数来导入翻译文件。
# 一旦翻译处理完成,函数会立即退出,返回状态码 0。
4)多进程设置
if config['workers']:
odoo.multi_process = True
# 如果配置中指定了 workers(工作进程数量),则将 Odoo 的 multi_process 标志设置为 True,以启用多进程模式。
5)启动服务器
stop = config["stop_after_init"]
#从config字典(它可能包含服务器的配置选项)中读取stop_after_init的值,并将其存储在变量stop中
setup_pid_file()
# 创建一个文件,并将当前进程的ID(PID)写入该文件
rc = odoo.service.server.start(preload=preload,
stop=stop)
#启动 Odoo 服务器。如果 stop 为 True,则在初始化后停止服务器。
sys.exit(rc)
#使用Python的sys模块的exit函数来退出程序。它接受一个参数(在这里是rc),该参数将被用作程序的退出状态
四、service目录下的 server.py
1. 全局变量和模块加载
global server
#声明全局变量
load_server_wide_modules()
#加载服务器级别的模块
2.选择服务器类型
根据配置和当前环境选择并初始化Odoo服务器的不同运行模式
1)事件驱动模式
if odoo.evented:
server = GeventServer(odoo.http.root)
#如果odoo.evented为真,则使用GeventServer来启动服务器,并传入odoo.http.root作为WSGI应用程序。
2)预分叉模式
elif config['workers']:
if config['test_enable'] or config['test_file']:
_logger.warning("Unit testing in workers mode could fail; use --workers 0.")
#检查是否启用了单元测试(config['test_enable']为真或config['test_file']有值)。如果是,它会发出一个警告
server = PreforkServer(odoo.http.root)
#如果配置中指定了多个工作进程(config['workers']),
则使用PreforkServer来启动服务器。
if sys.version_info[:2] == (3,5):
werkzeug.serving.WSGIRequestHandler.wbufsize = -1
#如果Python版本是3.5(由于Python 3.5中存在一个与WSGI处理相关的已知问题),代码会设置一个工作区(buffer)大小,以避免部分写入问题
3)线程模式(Threading Mode)
else:
if platform.system() == "Linux" and sys.maxsize > 2**32 and "MALLOC_ARENA_MAX" not in os.environ:
#在Linux上,如果Python是64位的(sys.maxsize > 2**32)且环境变量MALLOC_ARENA_MAX未设置
try:
import ctypes
libc = ctypes.CDLL("libc.so.6")
M_ARENA_MAX = -8
assert libc.mallopt(ctypes.c_int
(M_ARENA_MAX), ctypes.c_int(2))
#尝试使用ctypes和libc库来设置MALLOC_ARENA_MAX的值,以减少内存碎片
except Exception:
_logger.warning("Could not set ARENA_MAX through mallopt()")
server = ThreadedServer(odoo.http.root)
#条件都不满足或设置MALLOC_ARENA_MAX失败使用ThreadedServer来启动服务器
3.启用或禁用代码自动重新加载
watcher = None
#初始化watcher
if 'reload' in config['dev_mode'] and not odoo.evented:
#检查开发模式和事件驱动。如果 'reload' 存在于 config['dev_mode'] 中,并且当前环境不是事件驱动的(not odoo.evented)
if inotify:
watcher = FSWatcherInotify()
watcher.start()
#如果 inotify 模块可用(代码中没有直接检查,但假设 inotify 是一个布尔变量或类似的东西),则创建一个 FSWatcherInotify 实例,并启动它
elif watchdog:
watcher = FSWatcherWatchdog()
watcher.start()
#如果 inotify 不可用但 watchdog 模块可用(同样,代码中没有直接检查,但假设 watchdog 是一个布尔变量或类似的东西),则创建一个 FSWatcherWatchdog 实例,并启动它。
else:
if os.name == 'posix' and platform.system() != 'Darwin':
module = 'inotify'
else:
module = 'watchdog'
_logger.warning("'%s' module not installed. Code autoreload feature is disabled", module)
#根据操作系统类型,输出一个警告消息,说明哪个模块(inotify 或 watchdog)没有安装,并因此禁用了代码自动重新加载功能。
rc = server.run(preload, stop)
#无论是否启用了文件监视器,最后都会调用 server.run(preload, stop) 来启动服务器
if watcher:
watcher.stop()
#停止文件监视器 。如果之前已经创建了一个文件监视器(watcher 不是 None),那么调用它的 stop 方法来停止文件监视
if getattr(odoo, 'phoenix', False):
_reexec()
#检查并重新执行程序。使用了 getattr 函数来从 odoo 模块中获取一个名为 phoenix 的属性。如果 phoenix 属性存在并且其值为 True,那么程序将调用 _reexec() 函数来重新执行当前进程。
return rc if rc else 0
#返回退出码。最后,代码返回 rc(可能是服务器运行的结果或退出码)。如果 rc 为 None 或其他“假”值(如 0、False、空字符串等),则默认返回 0,这通常表示程序成功执行。
addons_path
#指定Odoo加载模块(addons)的目录路径
#路径可以是绝对路径,也可以是相对路径(相对于Odoo的根目录或其他基准目录)。
#多个路径之间使用逗号,分隔,没有空格。
#推荐的做法是将自定义模块或第三方模块放在与Odoo官方模块分开的目录中,以避免冲突和混淆。
#当Odoo启动时,它会按照addons_path中指定的顺序扫描和加载模块。因此,如果多个模块有相同的名称和功能,但位于不同的路径中,那么先被加载的模块将覆盖后面的模块。
odoo启动源码注释