1  make工具使用

1.1 makefile基本规则

Make工具最主要也是最基本的功能就是通过makefile文件来描述源程序之间的相互关系并自动维护编译工作。

Makefile的规则:

target ... : prerequisites ...
command
...
...

注意command如果不是在target那一行(一般都另起一行),则在command之前应先键入TAB符号,空格不行。

target是一个目标文件,它可以是执行文件,可以是Object File,也可以是一个标签

target这一个或多个的目标文件依赖于prerequisites中的文件,其生成规则定义在command中。

prerequisites中如果有一个以上的文件比target文件要新的话,command所定义的命令就会被执行

所以利用这个特点,如果是一个大项目只改了其中一个cpp文件,就可以只编译其中的某一部分即可,大大节省了编译时间。

makefile中的.PHONY目标的作用

使用.PHONY的两个理由是:

(1)避免和同名文件冲突

这个意思是比如当前makefile文件的目录下有跟目标target同名的目录或文件则会报错,在.PHONY目标上显示声明可以避免冲突

(2)改善性能

举个例子

clean:

rm *.o

由我们上面对makefile规则的理解,clean目标没有依赖目标,所以当真的存在clean文件时,则该clean文件一直都认为是最新的,所以执行make clean并不会执行clean下方的命令,这时就可以使用.PHONY指明该目标,比如:

.PHONY: clean

这样的话执行make clean命令,它将无视目标文件是否存在,跳过隐含规则搜索,直接执行clean下方的命令,所以这也就是它改善性能的原因,省略了隐含规则搜索这步

1.2  举例子

我们通过三个例子来讲解,由浅入深。

(1)

//main.cpp
#include <stdio.h>
int main(int argc, char** argv) {
printf("app startup\n");
printf("app stop\n");
return ;
}

Makefile可以这样编写:

main: main.o
g++ main.o -o main main.o: main.cpp
g++ -c main.cpp -o main.o clean:
rm -rf *.o main

当我们执行make命令时,make工具会执行到main目标,查看到它的依赖main.o,没有该文件,所以要先生成main.o,main.o目标的依赖是main.cpp,该文件存在,创建日期比main.o文件新,所以执行命令g++ -c main.cpp -o main.o生成main.o,再执行命令g++ main.o -o main生成main执行文件

clean是当执行make clean的时候会删除.o后缀文件和main文件,通常用来清理编译生成的文件

(2)

上面这个例子比较简单,那我们写个稍微比上面这个复杂一点的:

app.h文件:

#ifndef APP_H
#define APP_H class App{
public:
static App& getInstance();
bool start();
bool shutdown(); private:
App();
App(const App&);
App& operator=(const App&);
bool m_stopped;
}; #endif

app.cpp文件:

#include "app.h"
#include <stdio.h>
#include <unistd.h>
App& App::getInstance() {
static App app;
return app;
} App::App() {
m_stopped = false;
} bool App::start() {
printf("app startup\n");
while (!m_stopped) {
printf("app run\n");
sleep();
}
return true;
} bool App::shutdown() {
if (m_stopped == false) {
m_stopped = true;
}
return true;
}

main.cpp文件:

//main.cpp
#include <stdio.h> #include "app.h" int main(int argc, char** argv) {
App& app = App::getInstance(); if(!app.start()) {
printf("app start fail\n");
} app.shutdown();
return ;
}

因此我们可以这样写makefile:

main: main.o app.o
g++ main.o app.o -o main
main.o:main.cpp
g++ -c main.cpp -o main.o
app.o:app.cpp
g++ -c app.cpp -o app.o
clean:
rm -rf *.o main

通过上一个例子解释这个makefile很简单,但我们要想如果每个cpp文件都要这样写,或者每加一个cpp文件都要这样写,岂不是很麻烦,所以其实是可以借鉴一些正则匹配的思想,比如一个变量表示所有的cpp文件,可写出如下makefile:

CPP_SOURCES = $(wildcard *.cpp)
CPP_OBJS = $(patsubst %.cpp, %.o, $(CPP_SOURCES)) $(warning $(CPP_SOURCES))
$(warning $(CPP_OBJS)) default:compile $(CPP_OBJS):%.o:%.cpp
$(warning $<)
$(warning $@)
g++ -c $< -o $@ compile: $(CPP_OBJS)
g++ $^ -o main clean:
rm -f $(CPP_OBJS)
rm -f main

这里解释几个关键点:

wildcard函数的作用是把所有后缀匹配.cpp的文件以空格隔开返回给CPP_SOURCES变量保存,可以看到用$(warning $(CPP_SOURCES))语句打出变量值为app.cpp main.cpp

patsubst函数的作用是进行替换,将$(CPP_SOURCES)的变量值每一项由xx.cpp替换为xx.o

命令中的"$<"和"$@"则是自动化变量,"$<"表示所有的依赖目标集(也就是"main.cpp app.cpp"),"$@"表示目标集(也就是"main.o cpp.o")

"$^"表示所有的依赖目标集,表示main.o app.o

但上面这些makefile还是有缺点的,比如只支持cpp文件,.h和.cpp文件没有分离,.o文件全生成在当前目录下,没有支持第三方的库文件,包括include文件和lib文件

以下给出一个较完善的makefile文件:

TARGET = main
OBJ_PATH = objs CC = g++
CFLAGS = -Wall -Werror -g
LINKFLAGS = #INCLUDES = -I include/myinclude -I include/otherinclude1 -I include/otherinclude2
INCLUDES = -I include
#SRCDIR =src/mysrcdir src/othersrc1 src/othersrc2
SRCDIR = src
#LIBS = -Llib -lcurl -Llib -lmysqlclient -Llib -llog4cpp
LIBS = C_SRCDIR = $(SRCDIR)
C_SOURCES = $(foreach d,$(C_SRCDIR),$(wildcard $(d)/*.c) )
C_OBJS = $(patsubst %.c, $(OBJ_PATH)/%.o, $(C_SOURCES)) CPP_SRCDIR = $(SRCDIR)
CPP_SOURCES = $(foreach d,$(CPP_SRCDIR),$(wildcard $(d)/*.cpp) )
CPP_OBJS = $(patsubst %.cpp, $(OBJ_PATH)/%.o, $(CPP_SOURCES)) default:init compile $(C_OBJS):$(OBJ_PATH)/%.o:%.c
$(CC) -c $(CFLAGS) $(INCLUDES) $< -o $@ $(CPP_OBJS):$(OBJ_PATH)/%.o:%.cpp
$(CC) -c $(CFLAGS) $(INCLUDES) $< -o $@ init:
$(foreach d,$(SRCDIR), mkdir -p $(OBJ_PATH)/$(d);) compile:$(C_OBJS) $(CPP_OBJS)
$(CC) $^ -o $(TARGET) $(LINKFLAGS) $(LIBS) clean:
rm -rf $(OBJ_PATH)
rm -f $(TARGET) install: $(TARGET)
cp $(TARGET) $(PREFIX_BIN) uninstall:
rm -f $(PREFIX_BIN)/$(TARGET) rebuild: clean compile

当然makefile也不仅仅只用到编译上,任何想要做先后顺序执行脚本的事情我们都可以利用make来帮我们做,比如这个是我们项目中的makefile的一部分:

aodh:
cp -f SPECS/aodh/openstack-aodh.spec ~/rpmbuild/SPECS/
cp -f SPECS/aodh/* ~/rpmbuild/SOURCES/
tar zcvf ~/rpmbuild/SOURCES/aodh-4.0.3.tar.gz aodh-4.0.3 --exclude=".svn"
rpmbuild -bb ~/rpmbuild/SPECS/openstack-aodh.spec ceilometer:
cp -f SPECS/ceilometer/openstack-ceilometer.spec ~/rpmbuild/SPECS/
cp -f SPECS/ceilometer/* ~/rpmbuild/SOURCES/
tar zcvf ~/rpmbuild/SOURCES/ceilometer-8.1.4.tar.gz ceilometer-8.1.4 --exclude=".svn"
rpmbuild -bb ~/rpmbuild/SPECS/openstack-ceilometer.spec all_services:aodh ceilometer

当我们执行make aodh,就可以很方便的帮我们自动执行aodh下的脚本,执行make all_services时,根据makefile的规则,它会让aodh和ceilometer下的脚本都执行一次,这等同于我们的目标target是不存在的,所以每次都重新构建。

2  spec文件语法和使用

2.1  spec文件的基本知识

一般我们编译一个rpm编写spec文件是必不可少的,同时rpmbuild需要的以下5个目录也是必不可少的

BUILD:rpmbuild编译软件的目录,同时源码也会解压到该目录下

BUILDROOT:充当一个虚拟根目录,将要安装的文件放置到该虚拟目录下

SOURCES:放置源文件的目录

RPMS:用于存放编译好的RPM的目录

SRPMS:用以存放SOURCE RPM的目录

SPECS:用以存放spec文件

所有的预定义宏可在/usr/lib/rpm/macros文件中找到

这个目录下也还有其它定义的宏,比如systemd提供的spec文件中的宏放在/usr/lib/rpm/macros.d/macros.systemd文件中

也可以在shell下通过执行rpm –eval '%configure'命令来看configure这个宏的值,比如:

以下是spec的语法:

%{echo:message} :打印信息到标准输出,error是打印到标准错误,warn是打印警告信息到标准错误

%global name value :定义一个全局宏

可以用%macro_name或者%{macro_name}来调用,也可以扩展到shell,如

%define today %(date)

%{?macro_to_text:expression}:如果macro_to_text存在,expand expression,如果不存在,则输出为空;也可以逆着用:%{!?macro_to_text:expression}

%{?macro}:忽略表达式只测试该macro是否存在,如果存在就用该宏的值,如果不存在,就不用,如:./configure %{?_with_ldap}

%undefine macro :取消给定的宏定义

if else语句:

%global VVV 5

%if 0%{?VVV}

%{echo:19999}

%else

%{echo:29999}

%endif

这段是表示VVV这个全局变量有没有定义,如果有定义则输出19999,否则输出29999

if表达式里还可以使用!和&&等符号

用#来注释,如果注释内容里有%则需要%%转义,否则会报错

spec文件的基本写法:

Name: myapp    #设置该包服务的名字

Version: 1.1.2    #设置rpm包的版本号

Release:1        #设置rpm包的修订号

Group: System Environment/System      #设置rpm包的分类,所有组列在文件/usr/share/doc/rpm-version/GROUP,比如/usr/share/doc/rpm-4.11.3/GROUPS

Distribution: Red Hat Linux    #列出这个包属于那个发行版

Icon: file.xpm or file.gif        #存储在rpm包中的icon文件

Vendor: Company            #指定这个rpm包所属的公司或组织

URL:   #公司或组织的主页

Packager: sam shen <email>    #rpm包制作者的名字和email

License: LGPL            #包的许可证

Copyright: BSD            #包的版权

Summary: something descripe the package    #rpm包的简要信息

ExcludeArch: sparc s390        #rpm包不能在该系统结构下创建

ExclusiveArch: i386 ia64        #rpm包只能在给定的系统结构下创建

Excludeos:windows            #rpm包不能在该操作系统下创建

Exclusiveos: linux            #rpm包只能在给定的操作系统下创建

Buildroot: /tmp/%{name}-%{version}-root    #rpm包最终安装的目录,默认是/

Source0: telnet-client.tar.gz

Patch1:telnet-client-cvs.patch  #补丁文件

Patch2:telnetd-0.17.diff

Requires:bash>=2.0        #该包需要包bash,且版本至少为2.0,还有很多比较符号如<,>,<=,>=,=

PreReq: capability >=version    #capability包必须先安装

Conflicts:bash>=2.0            #该包和所有不小于2.0的bash包有冲突

BuildRequires:

BuildPreReq:

BuildConflicts:

#这三个选项和上述三个类似,只是他们的依赖性关系在构建包时就要满足,而前三者是在安装包时要满足

Autoreq: 0                 #禁用自动依赖

Prefix: /usr

#定义一个relocatable的包,当安装或更新包时,所有在/usr目录下的包都可以映射到其他目录,当定义Prefix时,所有%files标志的文件都要在Prefix定义的目录下

%triggerin --package < version

#当package包安装或更新时,或本包安装更新且package已经安装时,运行script

...script...

%triggerun --package

#当package包删除时,或本包删除且package已经安装时,运行script

(这里要注意的一点是这里的本包并不等于package包,package是随意定义的其他包的名字)

...script...

%triggerpostun --package

#当package包卸载后,或本包删除且package已经安装后,运行script

...script...

不过我在ceilometer项目中看到是这样的写法,是表示运行完后执行的段落:

%postun compute

%postun compute

%description:         #rpm包的描述

%prep                 #定义准备编译的命令 ,比如在项目中prep段落是执行%setup解压源码命令

%setup  -c            #在解压之前创建子目录

-q            #在安静模式下且最少输出

    -T            #禁用自动化解压包

    -n name      #设置子目录名字为name

-D            #在解压之前禁止删除目录

-a number        #在改变目录后,仅解压给定数字的源码,如-a 0 for source0

-b number        #在改变目录前,仅解压给定数字的源码,如-b 0 for source0

%patch -p0                #remove no slashes

%patch -p1                 #remove one slashes

%patch                #打补丁0

%patch1                #打补丁1

%build                #编译软件

比如一般c++程序的:

./configure  --prefix=$RPM_BUILD_ROOT/usr

make

一般python程序的:

%{__python2} setup.py build

%install              #安装软件

比如:make install PREFIX=$RPM_BUILD_ROOT/usr

比如python里的:%{__python2} setup.py install -O1 --skip-build --root %{buildroot}

install -d -m 755 %{buildroot}%{_sharedstatedir}/ceilometer

install可以在linux下用man install来看

install跟cp命令类似,但它可以控制文件权限属性,通常用于makefile中,基本使用格式:

install [OPTION]... [-T] SOURCE DEST

%clean                #清除编译和安装时生成的临时文件

比如:rm -rf $RPM_BUILD_ROOT

%post                 #定义安装之后执行的脚本

...script...

#rpm命令传递一个参数给这些脚本,1是第一次安装,>=2是升级,0是删除最新版本,用到的变量为$1,$2,$0

%preun                #定义卸载软件之前执行的脚本

...script...

%postun               #定义卸载软件之后执行的脚本

...script...

%files                #rpm包中要安装的所有文件列表

file1                 #文件中也可以包含通配符,如*

file2

directory             #所有文件都放在directory目录下

%dir   /etc/xtoolwait    #包含一个空目录/etc/xtoolwait 打进包里

%doc  /usr/X11R6/man/man1/xtoolwait.*    #安装该文档

%doc README NEWS            #安装这些文档到/usr/share/doc/ or /usr/doc

%docdir                    #定义存放文档的目录

%config /etc/yp.conf            #标志该文件是一个配置文件

%config(noreplace) /etc/yp.conf

#该配置文件不会覆盖已存在文件(被修改)覆盖已存在文件(没被修改),创建新的文件加上扩展后缀.rpmnew(被修改) ,比如我们不想升级后配置文件被改了,就可以用上noreplace

%config(missingok)    /etc/yp.conf    #该文件不是必须要的

%ghost  /etc/yp.conf            #该文件不应该包含在包中

%attr(mode, user, group)  filename    #控制文件的权限如%attr(0644,root,root) /etc/yp.conf,如果你不想指定值,可以用-

%config  %attr(-,root,root) filename    #设定文件类型和权限

%defattr(-,root,root)            #设置文件的默认权限

%lang(en) %{_datadir}/locale/en/LC_MESSAGES/tcsh*    #用特定的语言标志文件

%verify(owner group size) filename    #只测试owner,group,size,默认测试所有

%verify(not owner) filename        #不测试owner

#所有的认证如下:

#group:认证文件的组

#maj:认证文件的主设备号

#md5:认证文件的MD5

#min:认证文件的辅设备号

#mode:认证文件的权限

#mtime:认证文件最后修改时间

#owner:认证文件的所有者

#size:认证文件的大小

#symlink:认证符号连接

%verifyscript                #check for an entry in a system

...script...                #configuration file

这些verify用的少

%changelog

修改记录,类似这样

* Wed Mar 07 2018 RDO <dev@lists.rdoproject.org> 1:8.1.4-1

- Update to 8.1.4

如果在%package时用-n选项,那么在%description时也要用,如:

%description -n my-telnet-server

如果在%package时用-n选项,那么在%files时也要用

%package -n sub_package_name #定义一个子包,名字为sub_package_name

pushd、popd和dir对目录栈进行操作

可以看成这些命令在维护一个目录堆栈,堆栈的最上层一定是当前目录,且只有一个目录时不可popd出了,可用dirs来看当前目录栈情况,加上-c清空目录栈,-v可看到目录栈序号,pushd 目录x,可将目录x送入目录堆栈顶层,于是当前目录也会变成目录x,当pushd没有参数时,比如只执行pushd,则会把顶部两层目录交换,popd是pop出一个顶层目录出来,pushd +序号可以将这个目录推到栈目录顶部。

记住一点当前目录路径一定是栈目录的顶部目录路径。

所以在spec中也可以通过pushd和popd来改变当前工作目录

2.2  利用上面的知识制作一个简单的rpm

为了演示spec文件的灵活性,我们将c程序和python程序结合到一个spec文件来编译,但实际项目中肯定是要分成两个spec文件才是合理的。

该项目rpmbuild出来后会有两个rpm,分别是rpm1和rpm2,rpm1是打包了c应用服务文件,rpm2是打包了python的应用服务文件

首先利用tree命令看下我们的项目结构:

可以看到test_project下有两个目录(c_program和python_program)和一个spec文件,c_program文件夹里的内容就是我们上面make那里讲到的,python_program是使用python的打包部署工具setuptools来打包的,spec文件是我们的主要关注点,我们将其内容列出:

Name:            test_spec
Version: 1.0
Release:
Summary: pratise to make rpm Group: System Environment/System
License: GPL
URL: https://www.cnblogs.com/luohaixian/ Source0: test_project.tar.gz
Source1: xxx BuildArch: x86_64
BuildRequires: python-setuptools %description
pratise to make rpm
rpm1 c program
rpm2 python program # 定义一个子包rpm1
%package -n rpm1
Summary: make rpm1 Requires: gcc %description -n rpm1
xxxxxx # 定义一个子包rpm2
%package -n rpm2
Summary: make rpm2 %description -n rpm2
xxxxxx # 解压在Source0压缩包
# 源码文件都应先放置到~/rpmbuild/SOURCES目录下
%prep
%setup -q -n test_project # 执行编译
# 对于c_program的则利用它自己目录下的makefile写的编译规则进行编译
# 对于python_program的则利用它自己目录下的setup.py文件里的setup函数进行编译
# pushd在这里起到了类似cd的功能
%build
pushd c_program
make
popd pushd python_program
%{__python2} setup.py build
popd # 拷贝或安装编译好的文件到%{buildroot}目录下,这个目录我们可以看成是虚拟根目录
# 对于c_program的我们只需要安装一个main可执行文件到/usr/bin目录下
# 对于python_program我们使用python setup.py install来将python模块文件放置到/usr/lib/python/site-packages/目录下,注意这里一定要先切换到python_program目录下来执行
# 所以其实要装的文件都放到了虚拟根目录%{buildroot}下,然后由%files来决定哪些文件放置给哪个rpm
%install
mkdir -p %{buildroot}%{_bindir}
install -m $RPM_BUILD_DIR/test_project/c_program/main %{buildroot}%{_bindir}/
pushd python_program
%{__python2} setup.py install --root=%{buildroot}
popd # 定义rpm1安装之后执行的脚本,比如可以做启动服务等
%post -n rpm1 # 定义rpm2安装之后执行的脚本,比如可以做启动服务等
%post -n rpm2 # 定义rpm1包含的文件或文件夹
# 这里是定义了rpm1只包含一个main可执行文件
%files -n rpm1
%{_bindir}/main # 定义rpm2包含的文件或文件夹
# 这里是定义了rpm2包含了所有匹配%{python2_sitelib}/python_program*的文件夹和目录
%files -n rpm2
%{python2_sitelib}/python_program* %changelog
* Fri Sep <email> 1.0
- create spec

test_project的github地址:https://github.com/luohaixiannz/test_project

要将这个项目编译成两个rpm可以遵从如下步骤:

(1)创建rpmbuild所需要使用的目录,在~/目录下创建rpmbuild目录,然后再在rpmbuild目录下创建BUILD、BUILDROOT、SOURCES、SPECS、RPMS和SRPMS这6个子目录

(2)安装依赖包,rpmdevtools、python-setuptools、gcc、gcc-c++(可能还有些其它依赖包没说明,根据报错信息安装缺少的依赖包)

(3)将该压缩文件拷贝到~/rpmbuild/SOURCES目录下,将这个压缩文件里的test_project.spec文件拷贝到~/rpmbuild/SPECS目录下

(4)执行rpmbuild  -bb  ~/rpmbuild/SPECS/test_project.spec

3  打包openstack的项目为rpm包

可以通过在redhat网站上( http://vault.centos.org/)下openstack服务的对应版本的srpm文件,然后通过rpm2cpio命令结合cpio命令提取该srpm文件里的spec文件为己所用(除了spec文件,可能还包含了其它要用的文件,比如systemctl服务要用的.service文件),这样就不用耗费很大的精力去自己编写一个spec文件了。

比如我从openstack官网上获取了nova-15.0.0的项目源码(也可以直接使用srpm下解压出来的源码),想将其通过编译后打包成rpm,可通过如下步骤达到目的:

(1)从rethad网站上下srpm:wget http://vault.centos.org/7.4.1708/cloud/Source/openstack-ocata/openstack-nova-15.1.0-1.el7.src.rpm

(2)创建一个临时目录,比如test目录,cd test,然后执行:

rpm2cpio ../openstack-nova-15.1.0-1.el7.src.rpm | cpio -idv

接着就可以在当前目录下看到解压出来的文件了:

可以看到除了spec文件,还有很多的其它文件也是需要的,将这些文件都拷贝到~/rpmbuild/SPECS目录下

(3)执行rpmbuild  -bb  ~/rpmbuild/SPECS/openstack-nova.spec命令后就可以构建rpm了(可以需要装很多依赖包,根据报错将其装上就好了)

make和rpm的编译、打包总结的更多相关文章

  1. Android应用程序(APK)的编译打包过程

    (9878) (7) 现在很多人想对Android工程的编译和打包进行自动化,比如建立每日构建系统.自动生成发布文件等等.这些都需要我们对Android工程的编译和打包有一个深入的理解,至少要知道它的 ...

  2. build.xml配置编译打包过程(转)

    工程目录如下,使用eclipse中的ant对此工程进行编译打包: MonServer | --------src |            |--------com |            |--- ...

  3. Ant自动编译打包&发布 android项目

    Eclipse用起来虽然方便,但是编译打包android项目还是比较慢,尤其将应用打包发布到各个渠道时,用Eclipse手动打包各种渠道包就有点不切实际了,这时候我们用到Ant帮我们自动编译打包了. ...

  4. 项目androidAnt编译打包Android项目

    时间紧张,先记一笔,后续优化与完善. Ant编译打包Android项目 在Eclipse中对Android项目停止编译和打包如果项目比较大的话会比较慢,所以改为Ant工具来停止编译和打包 Ant环境配 ...

  5. webpack 配置 (支持 React SCSS ES6 编译打包 和 模块热更新 / 生成SourceMap)

    1.首先是目录结构 |-node_modules/ #包文件 |-build/ #静态资源生成目录 |-src/ #开发目录 |-js/ |-index.js #入口文件 |-app.js #Reac ...

  6. 通过ant脚本编译打包android工程

    通过ant脚本,编译打包android工程 1.Android程序编译.打包.签名.发布的三种方式:  方式一:命令行手动编译打包  方式二:使用ant自动编译打包  方式三:使用eclipse+AD ...

  7. Android-Ant自动编译打包android项目 -- 2 ----签名与渠道包

    上篇介绍了怎么使用ant自动编译打包现有的android项目,这篇将继续介绍如果如何在ant打包应用的时候加入签名信息以及自动打包渠道包. 1. 加入签名信息: 在项目的根目录下建一个ant.prop ...

  8. Android - Ant自动编译打包android项目 -- 1(转)

    1.  背景: Eclipse用起来虽然方便,但是编译打包android项目还是比较慢,尤其当要将应用打包发布到各个渠道时,用Eclipse手动打包各种渠道包就有点不切实际了,这时候我们用到Ant帮我 ...

  9. Storm-0.9.2-incubating源代码编译打包

    近期遇到一些同学询问Storm-0.9.2-incubating源代码编译打包的问题,现将编译步骤说明例如以下: 1.凝视掉project各pom文件里关于maven插件(maven-gpg-plug ...

  10. Java 编译打包命令

    背景 编译 打包 解压 运行 参考 背景 我们有的时候总是要使用将自己写的工程编译成 class 文件,同时打包成 jar,虽然有各种工具可以帮助我们,但是毕竟掌握使用 java 本来的命令去做这些更 ...

随机推荐

  1. 在SQL Server中,为何都建议禁止 VIA 协议,VIA协议具体内容是什么?

    在SQL Server 在SQL Server中,为何都建议禁止 VIA 协议,VIA协议具体内容是什么? 中,为何都建议禁止 VIA 协议,VIA协议具体内容是什么? 在SQL Server中,为何 ...

  2. zubax_gnss简介

    参考:https://docs.zubax.com/zubax_gnss 目录结构 bootloader:主要功能是升级application.加载application和初始化application ...

  3. linux各种服务的搭建

    https://blog.csdn.net/qq_33571718/article/details/81543408    VPN --linux服务搭建 https://blog.csdn.net/ ...

  4. mysql8.x 新版本jdbc连接方式

    旧版本,MySQL Connector/J 5.x 版本的连接方式:url = jdbc:mysql://localhost:3306/thrcloud_db01?useUnicode=true&am ...

  5. 六、MySQL系列之数据备份(六)

    本篇主要介绍用户授权.以及数据备份等知识: 一.用户授权 首先我们需要知道的是: 所有的用户及权限信息都存储在mysql数据库下的user表中,故我们可以通过查看user表的记录来查看用户权限信息,当 ...

  6. Django使用swagger生成接口文档

    参考博客:Django接入Swagger,生成Swagger接口文档-操作解析 Swagger是一个规范和完整的框架,用于生成.描述.调用和可视化RESTful风格的Web服务.总体目标是使客户端和文 ...

  7. 收起.NET程序的dll来

    作为上床后需要下床检查好几次门关了没有的资深强迫症患者,有一个及其搞我的问题,就是dll问题. 曾几何时,在没有nuget的年代,当有依赖项需要引用的时候,只能通过文件引用来管理引用问题,版本问题,更 ...

  8. (java)selenium webdriver学习--通过id、name定位,输入内容,搜索,关闭操作、通过tagname查找元素

    selenium webdriver学习--通过id.name定位,输入内容,搜索,关闭操作:通过tagname查找元素 打开谷歌浏览器,输入不同的网站,搜索框的定位含有不同元素(有时为id,有时为n ...

  9. 行为型模式(二) 命令模式(Command)

    一.动机(Motivate) 在我们的现实生活中有很多例子可以拿来说明这个模式,我们还拿吃饺子这个事情来说.我的奶奶说了,今天想吃饺子,发出了命令,然后我奶奶就去看电视去了.我们夫妻俩收到命令就开始和 ...

  10. python 杂记20191102

    上下文管理器: def __exit__(self, exc_type, exc_val, exc_tb):若exc_tb不是空,则说明有异常返回值只能是true或false,若是false,则会把之 ...