OpenHarmony编译构建流程概览[源码级]

前言

在看openharmony源码时发现和其他开源linux的比较大的区别是实现了多平台的支持和一些系统组件,而且是统一构建的,在做一些更改时也只能参考一些已有的文章,但稍微有点特殊的需求,便有点心虚不知如何下手了,尤其是编译构建的相关的问题,所以有了这篇源码剖析记录。

概述

编译构建框架以gn+ninja作为基础构建系统,(不熟悉gn和ninja的可以参考这篇文章,大概了解下gn快速入门笔记),针对产品需要和部件化功能,在gn阶段前增加了preloader和loader的预加载过程。

  1. preloader: 产品信息预加载。根据编译产品名称,加载对应的配置文件(config.json)并解析,将解析结果以文本方式输出到指定目录下。
  2. loader:子系统、部件和模块信息加载。以编译产品名称和preloader的输出作为输入,加载对应的subsystem、component、module/part等配置信息,以文本文件方式输出结果文件和gn的编译任务。
  3. gn: gn是一种编译构建工具,用于产生编译目标之间的图状编译依赖。在编译流程中,编译文件生成阶段负责构建参数赋值,执行gn gen编译命令,产生的build.ninja文件可以类比与makefile文件。
  4. ninja:ninja是一种接近汇编级的编译构建工具。语法规则简单,构建速度快。在编译流程中,根据编译规则完成对所有编译目标的编译过程。

build.sh脚本编译

OpenHarmony有两种编译方式,一种是通过hb工具编译,一种是通过build.sh脚本编译,而通过看代码我们会发现实际build.sh的方式最终也会调用的hb工具 ,所以说两种方式不是相互独立的,而是build.sh是hb的上层

  • build.sh的使用

build.sh的使用可参考官方给的说明

在这里想给出的一个说明是build.sh脚本可通过-h命令去查看使用方法,具体见附录一

由于系统编译过程比较慢,大家可以多多参考帮助信息,查看是否有适合自己的编译选项,例如可以使用如下命令只编译系统的组件hellonapi,

shell 复制代码
./build.sh --product-name rk3568 --ccache --build-target=hellonapi 

在本地没有修改gn和产品配置相关文件的前提下,添加--fast-rebuild会让你直接从ninja编译,可以加快编译过程,等等

  • build.sh源码梳理

在进行build.sh编译前首先需要执行 bash build/prebuilts_download.sh命令安装编译相关的环境,可以通过配置指定数据源,默认从华为提供的源中下载数据,并配置相关的环境。只支持linux和mac平台以及arm64和x86_64架构的CPU。

以下为build.sh脚本执行的大体流程,由于代码比较简单,就不再详细说明了

  1. 检测shell环境:支持linux下的支持,并将shell更改为dash

  2. 查询系统环境,并根据系统配置相关的一些环境变量(python、node、ohpm)

    npm config set registry https://repo.huaweicloud.com/repository/npm/
    

    npm(Node Package Manager)是Node.js的默认包管理器,用于发布、安装和管理JavaScript包(modules)

  3. 检测编译依赖的工具,不同的系统有不同的依赖,配置在文件(build/scripts/build_package_list.json)。

  4. 调用主编译程序(build/hb/main.py)

    /build/hb/main.py build $args_list

  • $args_list:输入的命令列表

到此处可发现后续就是采用的hb工具来实现的。

hb工具源码概览

hb是用python写的编译脚本,主函数目录如下:hb\main.py,以下为代码的主函数,主要执行过程可分为初始化和执行两部分。

python 复制代码
module_initializers = { #任务列表
            'build': main._init_indep_build_module if main._is_indep_build() else main._init_build_module,
            'init': main._init_hb_init_module,
            'indep_build': main._init_indep_build_module,
            'set': main._init_set_module,
            'env': main._init_env_module,
            'clean': main._init_clean_module,
            'tool': main._init_tool_module,
            'install': main._init_install_module,
            'package': main._init_package_module,
            'publish': main._init_publish_module,
            'update': main._init_update_module,
            'push': main._push_module
}
module_type = sys.argv[1] #获取设置的功能类型,例如build、set等
......
module = module_initializers[module_type]() #注:这是一个函数直接执行,也就是初始化过程
 try:
            module.run() #执行过程
            if module_type == 'build':
                LogUtil.hb_info('Cost Time:  {}'.format(SystemUtil.get_current_time() - start_time))
        except KeyboardInterrupt:
          	.......
            return -1
        else:
            return 0

在带参数调用hb时实际会执行 module = module_initializers[module_type]()这段代码从而实现对功能类别的筛选,做对应的初始化 ,例如执行hb build xxx时便会调用三目运算函数main._init_indep_build_module if main._is_indep_build() else main._init_build_module。本文的目的是做个概览,build这个参数和其他的参数的执行过程雷同,所以后面只说明build的这个参数的过程。从设计模式的角度看它就是个抽象工厂的模式

类图关系如下,详细分析的码友建议对着这个图片看:

如果有码友想自己生成的话可以参考根据python代码自动生成类图的实现方法[附带python源码],当然通过这个博客还不能完全做成这样,还需要简单的修改一下。

build初始化过程

可通过如下命令单独执行脚本:

shell 复制代码
/build/hb
$ python main.py  build --product-name purple_pi_oh --ccache

在build时可见首先执行main._init_indep_build_module if main._is_indep_build() else main._init_build_module,当_is_indep_build返回true时执行_init_indep_build_module,否则执行_init_build_module

--indep-build是一个用于独立构建的命令行选项。这个选项允许开发者单独编译和构建特定的组件或模块,而不是整个系统,详细信息可见indep_build选项说明。

由上图可知正常的流程是通过_init_build_module函数执行编译流程。

python 复制代码
def _init_build_module(self) -> BuildModuleInterface:
        args_dict = Arg.parse_all_args(ModuleType.BUILD)#1.解析构建的配置文件
        
        if args_dict.get("product_name").arg_value != '':# 如果配置了厂家名称
            set_args_dict = Arg.parse_all_args(ModuleType.SET) #解析配置文件setargs.json
            set_args_resolver = SetArgsResolver(set_args_dict) # 2.设置数据项的处理函数  
            ohos_set_module = OHOSSetModule(set_args_dict, set_args_resolver, "")
            ohos_set_module.set_product() #3.根据设置的厂家信息做匹配

        preloader = OHOSPreloader() # 4.产品信息预加载
        loader = OHOSLoader() # 5.子系统、部件和模块信息加载
        generate_ninja = Gn() # 6.编译构建工具,用于产生编译目标之间的图状编译依赖
        ninja = Ninja() #7.汇编级的编译构建工具
        build_args_resolver = BuildArgsResolver(args_dict)#8.设置数据项的处理函数,类似第2步
		# 所有的以上操作其实也只是为了生成对应的实例,提供给OHOSBuildModule进行初始化
        return OHOSBuildModule(args_dict, build_args_resolver, preloader, loader, generate_ninja, ninja)
1. 解析构建的配置文件

通过parse_all_args 函数利用json库 解析文件buildargs.json,并将解析的文件配置成argparse库的形式,并对数据类型及配置的数据做校验,便于后续配置应用。通过查看其他类型例如SET、ENV等过程也有类似的配置文件,配置方式也类似,如下:

下面选取了buildargs.json文件的一项,通过此项大概说明每个字段的含义。

json 复制代码
{
  "target_cpu": {
    "arg_name": "--target-cpu", //配置选项名称
    "argDefault": "arm", //配置项默认值
    "arg_help": "Default:''. Help:Specifies the desired cpu architecture for the build, each may support different cpu architectures, run 'hb set --all' to list product all supported cpu architectures", //帮助信息,实际的-h获取到的帮助信息也是直接从此文件中获取的
    "arg_phase": "prebuild",//包含prebuild、preload、load、preTargetGenerate、targetGenerate、postTargetGenerate、preTargetCompilation、targetCompilation、postTargetCompilation、postbuild,当前的编译参数通过统一的文本文件管理,保证参数功能独立,并一一映射对应的参数实现函数。在编译过程中,为了明确参数具体的作用时间,根据编译的preloader,loader,gn,ninja四个基础过程,人为划分了9个编译阶段
    "arg_type": "str", //包含bool、str、list、subparsers,决定了传入参数的类型
    "arg_attribute": {
      "optional": [ //选其中之一
        "arm",
        "arm64",
      .....//省略
      ]
    },
    "resolve_function": "resolve_target_cpu",//解决此选项的函数名称
    "testFunction": "testBuildTargetCpu" //测试此选项的函数名称
  },
 .......

此例子中有一项未提及,即"abbreviation"字段,此字段为简写说明。

arg_phase字段每个选项的含义如下:

shell 复制代码
PRE_BUILD                   # 编译预处理相关参数,如--full-compilation,--log-level
PRE_LOAD                    # preloader前置设置参数,
LOAD                        # loader前置设置参数,如--scalable-build,--build-xts
PRE_TARGET_GENERATE         # gn前置设置参数,如--gn-args
TARGET_GENERATE             # gn阶段参数
POST_TARGET_GENERATE        # gn后置设置参数
PRE_TARGET_COMPILATION      # ninja前置设置参数,如--ninja-arg
TARGET_COMPILATION          # ninja阶段参数
POST_TARGET_COMPILATION     # ninja后置设置参数,如--generate-ninja-trace
POST_BUILD                  # 编译后处理参数,如--clean-args
2.设置数据项的处理函数

将配置文件中的resolve_function字段对应的值设置为处理函数。

代码流程

  • hb\main.py
python 复制代码
 set_args_resolver = SetArgsResolver(set_args_dict)
    |-->SetArgsResolver:def __init__(self, args_dict: dict) #SetArgsResolver类的构造函数
        |-->super().__init__(args_dict) # 调用基类ArgsResolverInterface的构造函数
        	|-->self._args_to_function = dict() #创建函数字典
        	|-->self._map_args_to_function(args_dict) #设置回调函数名称到set_args_resolver中
ohos_set_module = OHOSSetModule(set_args_dict, set_args_resolver, "") #将获取的参数设置到ohos_set_module实例中
ohos_set_module.set_product() #
	|-->self.args_resolver.resolve_arg(self.args_dict['product_name'], self)
    	|-->resolve_function = self._args_to_function[target_arg.arg_name]
        |-->return resolve_function(target_arg, module) #调用回调函数

处理函数(让子类能够映射参数到解析函数上)

python 复制代码
def _map_args_to_function(self, args_dict: dict):
        for entity in args_dict.values(): #遍历字典
            if isinstance(entity, Arg) and entity.arg_name != 'sshkey':#检查每个值是否是 Arg 类型且参数名不是 'sshkey'
                args_name = entity.arg_name
                function_name = entity.resolve_function
                if not hasattr(self, function_name) or \
                        not hasattr(self.__getattribute__(function_name), '__call__'):#来获取方法,并检查它是否是可调用的,
                    raise OHOSException(
                        'There is no resolution function for arg: {}'.format(
                            args_name),
                        "0004")
                entity.resolve_function = self.__getattribute__(function_name)#解析函数赋值给 entity.resolve_function
                self._args_to_function[args_name] = self.__getattribute__(
                    function_name)#解析函数存储在 _args_to_function 字典中

此处有个疑问__getattribute__如何获取到的解析函数呢?这个就是多态,基类访问派生类,有兴趣的可以使用dir()函数查看基类中都包含哪些属性,在本示例中是包含resolve_product_name和resolve_set_parameter这两个派生类(SetArgsResolver)中的静态成员函数的。为了更好的说明这点我写了demo,大家有兴趣的可以看这个文章openharmony编译过程(python)中如何实现在配置文件中配置相关的实现函数的?

3. 根据设置的厂家信息做匹配

主要关注的函数为hb\resolver\set_args_resolver.py:resolve_product_name.

python 复制代码
def resolve_product_name(target_arg: Arg, set_module: SetModuleInterface):
    |-->config = Config() #获取配置文件信息,并将resources/config/config.json文件拷贝到out/ohos_config.json为了和下文的config区分,后续写的都会用ohos_config.json表示此处的配置文件
    |--> .......
	|-->get_product_info(product_name: str, company=None)
		|-->ProductUtil.get_products()
			|--> config_path = os.path.join(p_config_path, 'config.json') #获取配置文件\vendor\厂家名\产品名\config.json

涉及配置文件及对应解释如下:

  • config.json
json 复制代码
{
  "product_name": "purple_pi_oh", #产品名称
  "device_company": "rockchip", #厂家名称
  "device_build_path": "device/board/industio/purple_pi_oh", #设备子系统目录,preloader阶段从该目录下加载对应子系统部件配置信息。
  "target_cpu": "arm",#表示目标平台的CPU类型
  "type": "standard",#包含mini、small、standard
  "version": "3.0",
  "board": "purple_pi_oh",
  "api_version": 8,
  "enable_ramdisk": true,
  "enable_absystem": false,
  "build_selinux": true,
  "build_seccomp": true,
  "inherit": [ "productdefine/common/inherit/rich.json", "productdefine/common/inherit/chipset_common.json" ],
  "subsystems": [
    {
      "subsystem": "security",
      "components": [
        {
          "component": "selinux_adapter",
          "features": []
        }
      ]
    },
  	......
  ]
 }
  • ohos_config文件信息

生成在out目录下的ohos_config文件内容如下,默认文件(resources/config/config.json)中都是默认空值。

json 复制代码
{
  "root_path": "/home/zcc/3566",
  "board": "purple_pi_oh",
  "kernel": null,
  "product": "purple_pi_oh",
  "product_path": "/home/zcc/3566/vendor/industio/purple_pi_oh",
  "device_path": "/home/zcc/3566/device/board/rockchip/purple_pi_oh",
  "device_company": "rockchip",
  "os_level": "standard",
  "version": "3.0",
  "patch_cache": null,
  "product_json": "/home/zcc/3566/vendor/industio/purple_pi_oh/config.json",
  "component_type": "",
  "product_config_path": "/home/zcc/3566/vendor/industio/purple_pi_oh",
  "target_cpu": "arm",
  "target_os": null,
  "out_path": "/home/zcc/3566/out/purple_pi_oh",
  "subsystem_config_json": "build/subsystem_config.json",
  "device_config_path": "/home/zcc/3566/device/board/rockchip/purple_pi_oh",
  "support_cpu": null,
  "precise_branch": null,
  "compile_config": null,
  "log_mode": "normal"
}

解析配置文件有多个目录,略有不同,具体的区别或者说这样做的目的,还不是很清楚,需要详细了解的可参照函数(hb\util\product_util.py:def get_products()) 根据我实测发现我这个配置执行的代码如下:

python 复制代码
 if config.vendor_path != '':#默认目录为代码根目录的vendor文件夹
            for company in os.listdir(config.vendor_path):#遍历每个厂家的目录,可避免必须配置厂家信息
                company_path = os.path.join(config.vendor_path, company)
                if not os.path.isdir(company_path):
                    continue

                for product in os.listdir(company_path):#遍历厂家的每个产品列表
                    product_path = os.path.join(company_path, product)
                    config_path = os.path.join(product_path, 'config.json')

                    if os.path.isfile(config_path):
                        info = IoUtil.read_json_file(config_path)
                        product_name = info.get('product_name')
                        if product_name is not None:
                            yield {
                                'company': company,
                                "name": product_name,
                                'product_config_path': product_path,
                                'product_path': product_path,
                                'version': info.get('version', '3.0'),
                                'os_level': info.get('type', "mini"),
                                'config': config_path,
                                'component_type': info.get('component_type', '')
                            }
4.产品信息预加载

代码梳理:

python 复制代码
preloader = OHOSPreloader() 
	|-->OHOSPreloader:def __init__(self):
		|-->PreloadInterface:def __init__()
			|--> self._config = Config()# 获取配置文件信息
    |-->OHOSPreloader:self._toolchain_label = ""
    |-->.......

通过以上代码可发现此处主要是对OHOSPreloader类默认参数的初始化,并将配置信息赋值给成员变量_config。

5.子系统、部件和模块信息加载

代码梳理

python 复制代码
class OHOSLoader(LoadInterface):
    |-->super().__init__()
    	|--> LoadInterface:self._config = Config()# 获取配置文件信息
    |-->self.source_root_dir = "" #初始化默认参数
    |-->.......

通过以上代码可发现此处主要是对OHOSPreloader类默认参数的初始化,并将配置信息赋值给成员变量_config。

6.编译构建工具,用于产生编译目标之间的图状编译依赖

配置Gn执行文件

python 复制代码
class Gn(BuildFileGeneratorInterface):
    |-->......
    |-->self.config = Config()# 获取配置文件信息
    |-->self._regist_gn_path()# 设置gn工具的目录
7.汇编级的编译构建工具

配置ninja执行文件

python 复制代码
class Ninja(BuildExecutorInterface):
    def __init__(self):
        super().__init__()#创建实例对象
        self.config = Config()#获取配置文件
        self._regist_ninja_path()#配置ninja的执行文件目录

build执行过程

执行过程主要是根据初始化部分解析的配置文件(buildargs.json),然后根据配置文件中配置的不同阶段(PRE_BUILD、PRE_LOAD等)进行执行的。

主要的执行过程可参考类BuildModuleInterface的run函数,

python 复制代码
def run(self):
        try:
            self._prebuild_and_preload()
            self._load()
            self._gn()
            self._ninja()
        except OHOSException as exception:
            raise exception
        else:
            self._post_target_compilation()
        finally:
            self._post_build()

它会对初始化过程中的回调函数进行回调例如_load函数会回调到子类(OHOSBuildModule)的_load函数中。

python 复制代码
def _load(self):
        self._run_phase(BuildPhase.LOAD)#执行初始化过程中的回调函数
        if self.args_dict.get('fast_rebuild', None) and not self.args_dict.get('fast_rebuild').arg_value:
            self.loader.run()

第二行的代码会回调初始化过程中配置文件(buildargs.json)中的函数。对于这部分回调就和初始化的"2.设置数据项的处理函数"对应上了。

执行过程如下:

python 复制代码
def _run_phase(self, phase: BuildPhase):
        '''Description: Traverse all registered parameters in build process and 
            execute the resolver function of the corresponding phase
        @parameter: [phase]:  Build phase corresponding to parameter
        @return :none
        '''
        for phase_arg in [arg for arg in self.args_dict.values()if arg.arg_phase == phase]:
            self.args_resolver.resolve_arg(phase_arg, self)

执行过程流程图如下:

附件一编译命令支持选项

shell 复制代码
-h, --help  show this help message and exit
--target-cpu {arm,arm64,x86_64,x64,mipsel,riscv64,loongarch64} 
Default:''. Help:Specifies the desired cpu architecture for the build, each may support different cpu architectures, run 'hb set --all' to list product all supported cpu architectures
--target-os {android,ios} 
Default:''. Help:Specifies the desired os type for the build, each may support different os type, run 'hb set --all' to list product all supported os type
-p PRODUCT_NAME, --product-name PRODUCT_NAME 
Default:''. Help:Build a specified product. You could use this option like this: 1.'hb build --product-name rk3568@hihope' 2.'hb build --product-name rk3568'
--rename-last-log [RENAME_LAST_LOG]
Default:True. Help:You can use it to decide whether to keep the last build log
--log-mode {normal,silent}
 Default:'normal'. Help:You can use this option to determine whether to use single-line refresh log mode
--precise-branch PRECISE_BRANCH
Default:'dayu200_tdd'. Help:You can use this option to select the dayu200_tdd branch
--ccache [CCACHE] 
Default:True. Help:Enable ccache, this option could improve compilation speed. --stat-ccache can summary the cache data
--xcache [XCACHE] 
Default:False. Help:Enable xcache, this option could improve compilation speed. --stat- ccache can summary the cache data
--enable-pycache [ENABLE_PYCACHE]
Default:False. Help:Enable pycache, This option can improve the execution speed of pytho files
--jobs JOBS
Deprecated, please do not use this option
--disable-part-of-post-build [DISABLE_PART_OF_POST_BUILD ...]
Deprecated, please do not use this option
-T [BUILD_TARGET ...], --build-target [BUILD_TARGET ...]
Default:[]. Help:You use this option to specify a single compilation target, and use 'hb tool --ls' to list all build target
--ninja-args [NINJA_ARGS ...]
Default:[]. Help:You can use it to pass parameters for the ninja phase, but you need to follow the specified command format. eg. --ninja-args=-dkeeprsp
-f [FULL_COMPILATION], --full-compilation [FULL_COMPILATION]
Default:[]. Help:You can use it to start full code compilation. The default compilation target is images. Use this option to add 'make_all' and 'make_test' to the build process.
--strict-mode [STRICT_MODE]
Default:False. Help:Check all produce of each phase to early terminates a potentially
 problematic compilation.
--scalable-build [SCALABLE_BUILD]
Default:False. Help:Select whether to read information from parts.json generate by preload
--build-example [BUILD_EXAMPLE]
 Default:False. Help:Select whether to read information from subsystem_config_example.json
--build-platform-name BUILD_PLATFORM_NAME
Default:'phone'. Help:Name of the compilation platform. The current optional value is
'phone'
--build-xts [BUILD_XTS]
Default:False. Help:Select whether to load the components included in the subsystem xts
--ignore-api-check [IGNORE_API_CHECK ...]
Default:[]. Help:Skip the check of some subsystems
--load-test-config [LOAD_TEST_CONFIG]
Default:True. Help:Select whether to load the test field in bundle.json, that is, whether to call the test case
--skip-partlist-check [SKIP_PARTLIST_CHECK]
Default:False. Help:Skip the subsystem and component check in partlist file
--build-type {release,profile,debug}
Default:'release'. Help:Specify compile release or debug version
--log-level {info,debug}
Default:'INFO'. Help:Specify the log level during compilation. you can select two levels:debug, info. In debug mode, it show all command lines while building, including cxx, link, solink, etc.
--export-para [EXPORT_PARA ...]
Deprecated, please do not use this option
--test [TEST ...] 
Default:[]. Help:You can use it to choose test type. eg. --test xts
--gn-args [GN_ARGS ...]
Default:[]. Help:Specify gn build arguments, you could use this option like this 'hb build --gn-args is_debug=true'
--gn-flags [GN_FLAGS ...]
Default:[]. Help:Specify gn build arguments, you could use this option like this 'hb build --gn-flags "--export-compile-commands"
-c COMPILER, --compiler COMPILER
Deprecated, please do not use this option
--fast-rebuild [FAST_REBUILD]
Default:False. Help:You can use it to skip prepare, preloader, gn_gen steps so we can enable it only when there is no change for gn related script
--root-perf-main {root,main,root_main}
 Default:root. Help:different kinds of root packages
--runtime-mode {release,debug,profile}
 Default:release. Help:runtime mode
--check-compilation-parameters [CHECK_COMPILATION_PARAMETERS]
Default:false. Help:check compilation parameters
--keep-ninja-going [KEEP_NINJA_GOING]
Default:False. Help:When you need to debug one specific target, you can use this option to keep ninja going to skip some possible error until 1000000 jobs fail
--build-only-load [BUILD_ONLY_LOAD]
Default:False. Help:Stop build until load phase ends
--build-only-gn [BUILD_ONLY_GN]
Default:False. Help:Stop build until gn phase ends
--build-variant {user,root}
Default:'root'. Help:specifies device operating mode
--device-type DEVICE_TYPE
 Default:'default'. Help:specifies device type
--disable-package-image [DISABLE_PACKAGE_IMAGE]
deprecated, please do not use this option
--archive-image [ARCHIVE_IMAGE]
Default:False. Help:archive image when build product complete
--patch [PATCH] 
Default:False. Help: Apply patches as per configuration in patch.yml, and handle rollback if needed.
--rom-size-statistics [ROM_SIZE_STATISTICS]
Default:False. Help:statistics on the actual rom size for each compiled component
--stat-ccache [STAT_CCACHE]
Default:True. Help:summary ccache hitrate, and generate ccache.log in ${HOME}/.ccache dir
--get-warning-list [GET_WARNING_LIST]
Default:True. Help:You can use it to collect the build warning and generate WarningList.txt in output dir
--generate-ninja-trace [GENERATE_NINJA_TRACE]
Default:True. Help:Count the duration of each ninja thread and generate the ninja trace file(build.trace.gz)
--compute-overlap-rate [COMPUTE_OVERLAP_RATE]
Default:True. Help:Compute overlap rate during the post build
--clean-args [CLEAN_ARGS]
Default:True. Help:clean all args that generated by this compilation while compilation
finished
--deps-guard [DEPS_GUARD]
Default:True. Help:simplify code, remove concise dependency analysis, and speed up rule checking
相关推荐
奔跑吧邓邓子3 分钟前
【Python爬虫(12)】正则表达式:Python爬虫的进阶利刃
爬虫·python·正则表达式·进阶·高级
码界筑梦坊26 分钟前
基于Flask的京东商品信息可视化分析系统的设计与实现
大数据·python·信息可视化·flask·毕业设计
pianmian127 分钟前
python绘图之箱型图
python·信息可视化·数据分析
csbDD1 小时前
2025年网络安全(黑客技术)三个月自学手册
linux·网络·python·安全·web安全
赔罪2 小时前
Python 高级特性-切片
开发语言·python
Huang兄3 小时前
鸿蒙-状态管理V1
华为·harmonyos
伊一大数据&人工智能学习日志3 小时前
selenium爬取苏宁易购平台某产品的评论
爬虫·python·selenium·测试工具·网络爬虫
说是用户昵称已存在3 小时前
Pycharm+CodeGPT+Ollama+Deepseek
ide·python·ai·pycharm
Fansv5873 小时前
深度学习-2.机械学习基础
人工智能·经验分享·python·深度学习·算法·机器学习