make和rpm的编译、打包总结
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的编译、打包总结的更多相关文章
- Android应用程序(APK)的编译打包过程
(9878) (7) 现在很多人想对Android工程的编译和打包进行自动化,比如建立每日构建系统.自动生成发布文件等等.这些都需要我们对Android工程的编译和打包有一个深入的理解,至少要知道它的 ...
- build.xml配置编译打包过程(转)
工程目录如下,使用eclipse中的ant对此工程进行编译打包: MonServer | --------src | |--------com | |--- ...
- Ant自动编译打包&发布 android项目
Eclipse用起来虽然方便,但是编译打包android项目还是比较慢,尤其将应用打包发布到各个渠道时,用Eclipse手动打包各种渠道包就有点不切实际了,这时候我们用到Ant帮我们自动编译打包了. ...
- 项目androidAnt编译打包Android项目
时间紧张,先记一笔,后续优化与完善. Ant编译打包Android项目 在Eclipse中对Android项目停止编译和打包如果项目比较大的话会比较慢,所以改为Ant工具来停止编译和打包 Ant环境配 ...
- webpack 配置 (支持 React SCSS ES6 编译打包 和 模块热更新 / 生成SourceMap)
1.首先是目录结构 |-node_modules/ #包文件 |-build/ #静态资源生成目录 |-src/ #开发目录 |-js/ |-index.js #入口文件 |-app.js #Reac ...
- 通过ant脚本编译打包android工程
通过ant脚本,编译打包android工程 1.Android程序编译.打包.签名.发布的三种方式: 方式一:命令行手动编译打包 方式二:使用ant自动编译打包 方式三:使用eclipse+AD ...
- Android-Ant自动编译打包android项目 -- 2 ----签名与渠道包
上篇介绍了怎么使用ant自动编译打包现有的android项目,这篇将继续介绍如果如何在ant打包应用的时候加入签名信息以及自动打包渠道包. 1. 加入签名信息: 在项目的根目录下建一个ant.prop ...
- Android - Ant自动编译打包android项目 -- 1(转)
1. 背景: Eclipse用起来虽然方便,但是编译打包android项目还是比较慢,尤其当要将应用打包发布到各个渠道时,用Eclipse手动打包各种渠道包就有点不切实际了,这时候我们用到Ant帮我 ...
- Storm-0.9.2-incubating源代码编译打包
近期遇到一些同学询问Storm-0.9.2-incubating源代码编译打包的问题,现将编译步骤说明例如以下: 1.凝视掉project各pom文件里关于maven插件(maven-gpg-plug ...
- Java 编译打包命令
背景 编译 打包 解压 运行 参考 背景 我们有的时候总是要使用将自己写的工程编译成 class 文件,同时打包成 jar,虽然有各种工具可以帮助我们,但是毕竟掌握使用 java 本来的命令去做这些更 ...
随机推荐
- IDEA使用@Data注解,类调用get、set方法标红的解决办法
1.在setting中,下载lombok插件,安装完成后重启idea
- 利用脚本一键执行脚本,创建SharePoint文档库列表
SharePoint基于文档库和列表上进行二次开发,生成新的文档库和新的列表模板 通过新的模板,创建新的文档库与列表 --定义site对象$site = SPSite http://dvt176/si ...
- Thread-specific data(TSD)线程私有数据
Thread-specific data(TSD)线程私有数据 http://blog.chinaunix.net/uid-26885237-id-3209913.html linux多线程编程中引入 ...
- web项目服务器安装及配置(虚拟机centOS7)
一.安装VMware(如需) 1.首先下载VMware虚拟机,地址: https://www.vmware.com/products/workstation-pro/workstation-pro-e ...
- vue使用vuex大体结构
store1.js const state = {} const mutations = {} const actions = {} const getters = {} export default ...
- vue-quill-editor 富文本框使用及上传图片到服务器
注:上传图片需要结合element-ui的upload上传 首先第一步:安装vue-quill-editor或quill两个模块 yarn add vue-quill-editor -D yarn a ...
- org.apache.shiro.session.UnknownSessionException: There is no session with id [xxxx]的解决方案
org.apache.shiro.session.UnknownSessionException: There is no session with id [xxxx]的解决方案 背景描述 Sprin ...
- instanceof 和isInstance
Java中的instanceof关键字 instanceof是Java的一个二元操作符,和==,>,<是同一类东东.由于它是由字母组成的,所以也是Java的保留关键字.它的作用是测试它左边 ...
- java8 lamb表达式对List排序
场景一:List<Long> 或其他泛型,非对象 List<Long> ids = new ArrayList(); ids.add(100000001L); ids.add( ...
- docker部署Redmine项目管理平台
1.下载镜像(自己用的3.4版本) docker pull redmine:3.4 docker pull mysql:5.7 2.运行 docker run -p 3306:3306 --name ...