From 1d82ec98a27959c3ea630221efb57221bf70f414 Mon Sep 17 00:00:00 2001 From: lipingEmmaSiguyi <1477412247@qq.com> Date: Thu, 16 Jun 2022 15:18:51 +0800 Subject: [PATCH 1/4] =?UTF-8?q?=E5=A2=9E=E5=8A=A0=E4=B8=A4=E4=B8=AA?= =?UTF-8?q?=E8=84=9A=E6=9C=AC=E7=94=A8=E4=BA=8E=E5=B0=86=E5=85=BC=E5=AE=B9?= =?UTF-8?q?=E6=80=A7=E6=B8=85=E5=8D=95=E7=BD=91=E9=A1=B5=E6=95=B0=E6=8D=AE?= =?UTF-8?q?=E5=AF=BC=E5=87=BA=E5=88=B0excel=E8=A1=A8=E6=A0=BC=E4=B8=AD?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- tools/data_collection/README.md | 18 ++++++++ .../get_ecology_compatiable_list.py | 38 +++++++++++++++++ .../get_kylinos_compatiable_list.py | 41 +++++++++++++++++++ 3 files changed, 97 insertions(+) create mode 100644 tools/data_collection/get_ecology_compatiable_list.py create mode 100644 tools/data_collection/get_kylinos_compatiable_list.py diff --git a/tools/data_collection/README.md b/tools/data_collection/README.md index a95084c..71bfa42 100644 --- a/tools/data_collection/README.md +++ b/tools/data_collection/README.md @@ -83,3 +83,21 @@ dnf install ... } ``` + +## get_ecology_compatiable_list.py + +### 用法 + +``` +python3 get_ecology_compatiable_list.py + 将https://ecology.chinauos.com上的兼容性清单导出到excel表格: ecology_uos_compatiable_list.xls +``` + +## get_kylinos_compatiable_list.py + +### 用法 + +``` +python3 get_kylinos_compatiable_list.py + 将https://eco.kylinos.cn上的兼容性清单导出到excel表格:kylinos_compatiable_list.xls +``` diff --git a/tools/data_collection/get_ecology_compatiable_list.py b/tools/data_collection/get_ecology_compatiable_list.py new file mode 100644 index 0000000..c5dc108 --- /dev/null +++ b/tools/data_collection/get_ecology_compatiable_list.py @@ -0,0 +1,38 @@ +#!/usr/bin/env python3 +# coding=utf-8 +import urllib3 +import json +import xlwt + +class EcologyUos: + def __init__(self): + self.all_data = {} + + def pull_all_data(self): + http = urllib3.PoolManager(10) + req = http.request('GET', 'https://ecology.chinauos.com/analysis/jingpin/search?page=1&limit=10&status=done&framework=all&url=&query=') + + encoded_data = json.loads(req.data.decode('utf-8')) + count = encoded_data["count"] + url = "https://ecology.chinauos.com/analysis/jingpin/search?page=1&limit=" + str(count) + "&status=done&framework=all&url=&query=" + req_all_data_hash = json.loads(http.request('GET', url).data.decode('utf-8')) + self.all_data = req_all_data_hash + + + def write_to_xlsx(self): + sheet_data = self.all_data["data"] + workbook = xlwt.Workbook(encoding='utf-8') + sheet1 = workbook.add_sheet("统信兼容性清单") + for col, head in enumerate(sheet_data[0].keys()): + sheet1.write(0, col, head) + + for row, sheet_row in enumerate(sheet_data): + for col, row_key in enumerate(sheet_row.keys()): + sheet1.write(row+1, col, sheet_row[row_key]) + + workbook.save('./ecology_uos_compatiable_list.xls') + +if __name__ == '__main__': + eu = EcologyUos() + eu.pull_all_data() + eu.write_to_xlsx() diff --git a/tools/data_collection/get_kylinos_compatiable_list.py b/tools/data_collection/get_kylinos_compatiable_list.py new file mode 100644 index 0000000..60f222c --- /dev/null +++ b/tools/data_collection/get_kylinos_compatiable_list.py @@ -0,0 +1,41 @@ +#!/usr/bin/env python3 +# coding=utf-8 +import urllib3 +import json +import xlwt + +class EcologyUos: + def __init__(self): + self.all_data = [] + + def pull_all_data(self): + http = urllib3.PoolManager(10) + req = http.request('GET', 'https://eco.kylinos.cn/home/compatible/index.html?system_class=1&system_id=&small_version_id=&is_plan=0&page=1&limit=20') + + encoded_data = json.loads(req.data.decode('utf-8')) + count = encoded_data["count"] + page_num = (count // 20) + 1 + req_all_data_list = [] + for i in range(page_num): + url = "https://eco.kylinos.cn/home/compatible/index.html?system_class=1&system_id=&small_version_id=&is_plan=0&page=" + str(i+1) + "&limit=20" + req_data = json.loads(http.request('GET', url).data.decode('utf-8'))["data"] + req_all_data_list.extend(req_data) + self.all_data = req_all_data_list + + def write_to_xlsx(self): + sheet_data = self.all_data + workbook = xlwt.Workbook(encoding='utf-8') + sheet1 = workbook.add_sheet("麒麟软件兼容性清单") + for col, head in enumerate(sheet_data[0].keys()): + sheet1.write(0, col, head) + + for row, sheet_row in enumerate(sheet_data): + for col, row_key in enumerate(sheet_row.keys()): + sheet1.write(row+1, col, sheet_row[row_key]) + + workbook.save('./kylinos_compatiable_list.xls') + +if __name__ == '__main__': + eu = EcologyUos() + eu.pull_all_data() + eu.write_to_xlsx() -- Gitee From 2a079c895cc0e0bebde935228ae9764109efa554 Mon Sep 17 00:00:00 2001 From: lipingEmmaSiguyi <1477412247@qq.com> Date: Fri, 8 Jul 2022 10:24:10 +0800 Subject: [PATCH 2/4] modify get_kylinos_compatiable_list.py [why] The kylinos compatiable list is too long, need to enable multi-threaded data crawling to improve efficiency. --- .../get_kylinos_compatiable_list.py | 122 +++++++++++++----- 1 file changed, 87 insertions(+), 35 deletions(-) diff --git a/tools/data_collection/get_kylinos_compatiable_list.py b/tools/data_collection/get_kylinos_compatiable_list.py index 60f222c..94adacc 100644 --- a/tools/data_collection/get_kylinos_compatiable_list.py +++ b/tools/data_collection/get_kylinos_compatiable_list.py @@ -2,40 +2,92 @@ # coding=utf-8 import urllib3 import json -import xlwt - -class EcologyUos: - def __init__(self): - self.all_data = [] - - def pull_all_data(self): - http = urllib3.PoolManager(10) - req = http.request('GET', 'https://eco.kylinos.cn/home/compatible/index.html?system_class=1&system_id=&small_version_id=&is_plan=0&page=1&limit=20') - - encoded_data = json.loads(req.data.decode('utf-8')) - count = encoded_data["count"] - page_num = (count // 20) + 1 - req_all_data_list = [] - for i in range(page_num): - url = "https://eco.kylinos.cn/home/compatible/index.html?system_class=1&system_id=&small_version_id=&is_plan=0&page=" + str(i+1) + "&limit=20" - req_data = json.loads(http.request('GET', url).data.decode('utf-8'))["data"] - req_all_data_list.extend(req_data) - self.all_data = req_all_data_list - - def write_to_xlsx(self): - sheet_data = self.all_data - workbook = xlwt.Workbook(encoding='utf-8') - sheet1 = workbook.add_sheet("麒麟软件兼容性清单") - for col, head in enumerate(sheet_data[0].keys()): - sheet1.write(0, col, head) - - for row, sheet_row in enumerate(sheet_data): - for col, row_key in enumerate(sheet_row.keys()): - sheet1.write(row+1, col, sheet_row[row_key]) - - workbook.save('./kylinos_compatiable_list.xls') +import openpyxl +from queue import Queue +import threading + +''' +整体的思路: +1、构造任务队列pageQueue ,存放所有要爬取的页面url +2、用多线程爬虫,然后将抓取的页面内容存放到all_data中 +3、然后存放到的xlsx文件中 +''' + +class Crawl_thread(threading.Thread): + ''' + 抓取线程类,注意需要继承线程类Thread + ''' + def __init__(self,thread_id,queue): + threading.Thread.__init__(self) # 需要对父类的构造函数进行初始化 + self.thread_id = thread_id + self.queue = queue # 任务队列 + + def run(self): + ''' + 线程在调用过程中就会调用对应的run方法 + :return: + ''' + print('启动线程:',self.thread_id) + self.crawl_spider() + print('退出了该线程:',self.thread_id) + + def crawl_spider(self): + while True: + if self.queue.empty(): #如果队列为空,则跳出 + break + else: + page = self.queue.get() + print("第 %d 页" %(page)) + print('当前工作的线程为:',self.thread_id," 正在采集:",page) + try: + http = urllib3.PoolManager(10) + url = "https://eco.kylinos.cn/home/compatible/index.html?system_class=1" \ + "&system_id=&small_version_id=&is_plan=0&page=" + str(page) + "&limit=20" + req_data = json.loads(http.request('GET', url).data.decode('utf-8'))["data"] + all_data.extend(req_data) + print("数据队列长度:",len(all_data),end="\n") + except Exception as e: + print('采集线程错误',e) + +all_data = [] +def main(): + http = urllib3.PoolManager(10) + req = http.request('GET', 'https://eco.kylinos.cn/home/compatible/index.html?' + 'system_class=1&system_id=&small_version_id=&is_plan=0&page=1&limit=20') + + encoded_data = json.loads(req.data.decode('utf-8')) + count = encoded_data["count"] + page_num = (count // 20) + 1 + pageQueue = Queue(count) # 任务队列,存放网页的队列 + for page in range(1, page_num+1): + pageQueue.put(page) # 构造任务队列 + # 初始化采集线程 + crawl_threads = [] + crawl_name_list = ['crawl_' + str(i) for i in range(1, 6)] # 总共构造5个爬虫线程 + for thread_id in crawl_name_list: + thread = Crawl_thread(thread_id, pageQueue) # 启动爬虫线程 + thread.start() # 启动线程 + crawl_threads.append(thread) + + # 等待队列情况,先进行网页的抓取 + while not pageQueue.empty(): + # 不为空,则继续阻塞 + pass + + # 等待所有线程结束 + for t in crawl_threads: + t.join() + + wb = openpyxl.Workbook() + sheet = wb.active + + for col, head in enumerate(all_data[0].keys()): + sheet.cell(row=1, column=col+1, value=head) + + for row, sheet_row in enumerate(all_data): + for col, row_key in enumerate(sheet_row.keys()): + sheet.cell(row=row+2, column=col+1, value=sheet_row[row_key]) + wb.save(filename="kylinos_compatiable_list.xlsx") if __name__ == '__main__': - eu = EcologyUos() - eu.pull_all_data() - eu.write_to_xlsx() + main() -- Gitee From 2e37bd69ed7d7824d9cf58e05d3e5afd031e4311 Mon Sep 17 00:00:00 2001 From: lipingEmmaSiguyi <1477412247@qq.com> Date: Mon, 11 Jul 2022 15:01:11 +0800 Subject: [PATCH 3/4] add 2 scripts to crawl suse hardware data and top 100 docker. --- tools/data_collection/get_docker_info.py | 88 +++++++++ .../get_suse_hardware_compatiable_list.py | 173 ++++++++++++++++++ 2 files changed, 261 insertions(+) create mode 100644 tools/data_collection/get_docker_info.py create mode 100644 tools/data_collection/get_suse_hardware_compatiable_list.py diff --git a/tools/data_collection/get_docker_info.py b/tools/data_collection/get_docker_info.py new file mode 100644 index 0000000..c8886a7 --- /dev/null +++ b/tools/data_collection/get_docker_info.py @@ -0,0 +1,88 @@ +# coding=utf-8 +import json +import openpyxl +import requests +import re + +INLINE_LINK_RE = re.compile(r'\[([^\]]+)\]\(([^)]+)\)') +FOOTNOTE_LINK_TEXT_RE = re.compile(r'\[([^\]]+)\]\[(\d+)\]') +FOOTNOTE_LINK_URL_RE = re.compile(r'\[(\d+)\]:\s+(\S+)') + +class Spider: + def __init__(self): + self.url = "https://hub.docker.com/api/content/v1/products/search?operating_system=linux&page_size=25&q=" + self.response = [] + + def pull_all_data(self): + payload = {} + headers = { + 'Accept': 'application/json', + 'Search-Version': 'v3' + } + response = json.loads(requests.request("GET", self.url, headers=headers, data=payload).text) + page_size = response["page_size"] + numFound = 100 + page_num = numFound // page_size + for i in range(1, page_num+1): + url = "https://hub.docker.com/api/content/v1/products/search?operating_system=linux&page_size=25&page=" + str(i) + payload = {} + headers = { + 'Accept': 'application/json', + 'Search-Version': 'v3' + } + response = json.loads(requests.request("GET", url, headers=headers, data=payload).text) + next_url = "" + for item in response['summaries']: + name = item['name'] + if name.split("/")[0] == item['name']: + name = "-".join([i.lower() for i in name.split(" ")]) + next_url = "https://hub.docker.com/v2/repositories/library/" + name + else: + next_url = "https://hub.docker.com/v2/repositories/" + name.split('/')[0] + "/" + name.split('/')[1] + "/" + item["dockerhub_url"] = next_url + info = self.get_more_information(next_url) + dictMerged2 = dict(item, **info) + self.response.append(dictMerged2) + + def get_more_information(self,url): + response = json.loads(requests.request("GET", url).text) + links = {} + if response.get("full_description"): + links = self.find_md_links(response['full_description']) + return links + + def find_md_links(self, md): + """ Return dict of links in markdown """ + + links = dict(INLINE_LINK_RE.findall(md)) + footnote_links = dict(FOOTNOTE_LINK_TEXT_RE.findall(md)) + footnote_urls = dict(FOOTNOTE_LINK_URL_RE.findall(md)) + + for key, value in footnote_links.items(): + footnote_links[key] = footnote_urls[value] + links.update(footnote_links) + urls = {} + url_list = [] + for i in links.values(): + if "Dockerfile" in i: + url_list.append(i) + urls["urls"] = "\n".join(url_list) + + return urls + + def write_to_xlsx(self): + wb = openpyxl.Workbook() + sheet = wb.active + + for col, head in enumerate(self.response[0].keys()): + sheet.cell(row=1, column=col + 1, value=head) + + for row, sheet_row in enumerate(self.response): + for col, row_key in enumerate(sheet_row.keys()): + sheet.cell(row=row + 2, column=col + 1, value=str(sheet_row[row_key])) + wb.save(filename="docker_top_100.xlsx") + +if __name__ == '__main__': + sp = Spider() + sp.pull_all_data() + sp.write_to_xlsx() diff --git a/tools/data_collection/get_suse_hardware_compatiable_list.py b/tools/data_collection/get_suse_hardware_compatiable_list.py new file mode 100644 index 0000000..119be21 --- /dev/null +++ b/tools/data_collection/get_suse_hardware_compatiable_list.py @@ -0,0 +1,173 @@ +#!/usr/bin/env python3 +# coding=utf-8 +import urllib3 +from queue import Queue +import threading +import re +import html +import openpyxl + +''' +整体的思路: +1、构造任务队列pageQueue ,存放所有要爬取的页面url +2、用多线程爬虫将抓取的页面内容存放到data_queue中 +3、用多线程程序对data_queue中的页面内容进行解析,分别提取id, product, os, company, category, +''' + +class Crawl_thread(threading.Thread): + ''' + 抓取线程类,注意需要继承线程类Thread + ''' + def __init__(self,thread_id,queue): + threading.Thread.__init__(self) + self.thread_id = thread_id + self.queue = queue # 任务队列 + + def run(self): + ''' + 线程在调用过程中就会调用对应的run方法 + :return: + ''' + print('启动线程:',self.thread_id) + self.crawl_spider() + print('退出了该线程:',self.thread_id) + + def crawl_spider(self): + while True: + if self.queue.empty(): #如果队列为空,则跳出 + break + else: + page = self.queue.get() + print('当前工作的线程为:',self.thread_id," 正在采集:",page) + try: + http = urllib3.PoolManager(10) + url = "https://www.suse.com/nbswebapp/yesBulletin.jsp?bulletinNumber=" + str(page) + page_data = http.request('GET', url) + status_code = page_data.status + if status_code == 200: + page_html = page_data.data.decode('utf-8') + data_queue.put((page_html, page)) + except Exception as e: + print('采集线程错误',e) + +class Parser_thread(threading.Thread): + ''' + 解析网页的类,就是对采集结果进行解析,也是多线程方式进行解析 + ''' + def __init__(self,thread_id,queue): + threading.Thread.__init__(self) + self.thread_id = thread_id + self.queue = queue + + def run(self): + print('启动线程:', self.thread_id) + while not flag: + try: + item = self.queue.get(False) # get参数为false时队列为空,会抛出异常 + if not item: + pass + self.parse_data(item) + except Exception as e: + pass + print('退出了该线程:', self.thread_id) + def parse_data(self,item): + ''' + 解析网页内容的函数 + :param item: + :return: + ''' + try: + print("开始解析:", self.thread_id) + page = item[0] + id = item[1] + product = "" + category = "" + company = "" + os = "" + regex = re.compile('(.*?)', re.S) + result = regex.findall(page) + regex_category = re.compile('(.*?)', re.S) + result_category = regex_category.findall(page) + regex_company = re.compile('For more information regarding the specific test configuration, please contact:
(.*?)
(.*?)(.*?)', re.S) + result_company = regex_company.findall(page) + regex_os = re.compile('Operating Systems:(.*?)
(.*?)(.*?)(.*?)', re.S) + result_os = regex_os.findall(page) + + for item in result: + product += html.unescape(item.strip()).strip() + + for item in result_category: + category += html.unescape(item.strip()).strip() + + for item in result_company: + company += html.unescape(item[-1].strip()).strip() + + for item in result_os: + os += html.unescape(item[-1].strip()).strip() + + response = { + 'id': id, + 'product': product, + 'category': category, + 'company': company, + 'os': os + } + + all_data.append(response) + except Exception as e: + print('parse: ',e) + + +data_queue = Queue(50) +all_data = [] +flag = False +def main(): + pageQueue = Queue(100) # 任务队列,存放网页的队列 + for page in range(101400, 101500): + pageQueue.put(page) # 构造任务队列 + # 初始化采集线程 + crawl_threads = [] + crawl_name_list = ['crawl_' + str(i) for i in range(1, 6)] # 总共构造5个爬虫线程 + for thread_id in crawl_name_list: + thread = Crawl_thread(thread_id, pageQueue) # 启动爬虫线程 + thread.start() # 启动线程 + crawl_threads.append(thread) + + # 初始化解析线程 + parse_thread = [] + parser_name_list = ['parse_' + str(i) for i in range(1, 6)] + for thread_id in parser_name_list: + thread = Parser_thread(thread_id,data_queue) + thread.start() # 启动线程 + parse_thread.append(thread) + + # 等待队列情况,先进行网页的抓取 + while not pageQueue.empty(): + pass # 不为空,则继续阻塞 + + # 等待所有线程结束 + for t in crawl_threads: + t.join() + # 等待队列情况,对采集的页面队列中的页面进行解析,等待所有页面解析完成 + while not data_queue.empty(): + print("队列里面没有数据") + pass + # 通知线程退出 + global flag + flag = True + for t in parse_thread: + t.join() # 等待所有线程执行到此处再继续往下执行 + + wb = openpyxl.Workbook() + sheet = wb.active + + for col, head in enumerate(all_data[0].keys()): + sheet.cell(row=1, column=col+1, value=head) + + for row, sheet_row in enumerate(all_data): + for col, row_key in enumerate(sheet_row.keys()): + sheet.cell(row=row+2, column=col+1, value=sheet_row[row_key]) + wb.save(filename="suse_compatiable_list.xlsx") + +if __name__ == '__main__': + main() -- Gitee From 874d38dfe271bfcd1baff6777f26b75b3300cff7 Mon Sep 17 00:00:00 2001 From: lipingEmmaSiguyi <1477412247@qq.com> Date: Thu, 8 Dec 2022 20:48:47 +0800 Subject: [PATCH 4/4] update software-compatible doc --- doc/software-compatibility/dist/oepkgs.png | Bin 0 -> 33661 bytes ...72\344\273\223\346\265\201\347\250\213.md" | 33 +++--- ...46\347\273\206\346\214\207\345\257\274.md" | 108 +++++++++++++----- 3 files changed, 93 insertions(+), 48 deletions(-) create mode 100644 doc/software-compatibility/dist/oepkgs.png diff --git a/doc/software-compatibility/dist/oepkgs.png b/doc/software-compatibility/dist/oepkgs.png new file mode 100644 index 0000000000000000000000000000000000000000..e1e48ed6fb1643a004b47f2d28ca68dfe1723852 GIT binary patch literal 33661 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://compass-ci.openeuler.org/jobs - +###### 2.1 可通过构建工程系统,以评论形式评论至仓库PR中 #### 3. rpmbuild脚本 在submit rpmbuild.yaml 时,测试用例**rpmbuild**会去引用脚本 @@ -125,8 +123,7 @@ os_version: $upstream_branch 如果构建成功,则通过upload_rpm_pkg函数先将测试机上打好的软件包放入```/srv/rpm/upload```,再通过update_repo_mq处理上传的软件包。处理完的包会先放入/srv/rpm/testing中,每天零点定时更新到/srv/rpm/pub中,也就是https://repo.oepkgs.net/openEuler/rpm/仓库中 #### 4. 测试构建的包能否正常安装 -###### 4.1 可以查看job_id(自动构建任务,无需提交,可通过job_id来查看日志,该job_id之后将由门禁系统,以评论形式评论至仓库PR中,目前暂无) -https://compass-ci.openeuler.org/jobs +###### 4.1 可以查看自动构建任务,无需提交,以评论形式评论至仓库PR中) ###### 4.2 手动提交install.yaml 需要加入以下参数 @@ -174,9 +171,9 @@ mount_repo_name: compatible/c7 ### 如何查询软件包位置? -[https://compass-ci.openeuler.org/oepkgs](https://compass-ci.openeuler.org/oepkgs) +[https://search.oepkgs.net/](https://search.oepkgs.net/) 可在此查询引入到软件所的软件包 ### 如何下载使用仓库中的软件包? -在[https://compass-ci.openeuler.org/oepkgs](https://compass-ci.openeuler.org/oepkgs) -查询软件包在软件所中的仓库存放位置之后,详见[openEuler社区开源软件适配流程.md](https://gitee.com/openeuler/oec-application/blob/master/doc/openEuler%E7%A4%BE%E5%8C%BA%E5%BC%80%E6%BA%90%E8%BD%AF%E4%BB%B6%E9%80%82%E9%85%8D%E6%B5%81%E7%A8%8B.md)的最后一节:**下载使用软件**,修改这一节中的示例中的**baseurl**即可。 +在[https://search.oepkgs.net/](https://search.oepkgs.net/) +查询软件包在软件所中的仓库存放位置之后,点开软件包的详情页,按照安装指引便可下载使用软件包。 diff --git "a/doc/\345\214\227\345\220\221\345\274\200\346\272\220\350\275\257\344\273\266\345\214\205\351\200\202\351\205\215\350\277\201\347\247\273\350\257\246\347\273\206\346\214\207\345\257\274.md" "b/doc/\345\214\227\345\220\221\345\274\200\346\272\220\350\275\257\344\273\266\345\214\205\351\200\202\351\205\215\350\277\201\347\247\273\350\257\246\347\273\206\346\214\207\345\257\274.md" index 1e3e20e..799f21d 100644 --- "a/doc/\345\214\227\345\220\221\345\274\200\346\272\220\350\275\257\344\273\266\345\214\205\351\200\202\351\205\215\350\277\201\347\247\273\350\257\246\347\273\206\346\214\207\345\257\274.md" +++ "b/doc/\345\214\227\345\220\221\345\274\200\346\272\220\350\275\257\344\273\266\345\214\205\351\200\202\351\205\215\350\277\201\347\247\273\350\257\246\347\273\206\346\214\207\345\257\274.md" @@ -1,58 +1,98 @@ -## 背景介绍 +[TOC] + +### 背景介绍 [oepkgs](https://oepkgs.net/zh/) 全称开放软件包服务(Open External Packages Service),是一个为 openEuler 以及其他 Linux 发行版提供软件包服务和容器镜像服务的第三方社区。 oepkgs 社区提供两种开源软件包适配方式,第一种方式开源软件包的源码合入 [src-oepkgs](https://gitee.com/src-oepkgs) 组织仓下面,由 src-oepkgs 的构建服务对软件包进行构建 测试,兼容性测试,并进入 oepkgs 的[主体仓库](https://repo.oepkgs.net/openEuler/rpm/)中。另一种方式用户通过网页快速构建软件包,软件包进入个人账户下面的某个仓库中。 +![输入图片说明](./software-compatibility/dist/oepkgs.png) ### 开源软件引入oepkgs主仓总体流程 ->**1. 获取到spec文件以及源码文件** +>**1. 初始化RPM编译环境** >**2. 在openEuler上进行编译构建** >**3. 在openEuler上进行兼容性测试** ->**4. 将已经适配好的spec文件以及源码文件存放在src-oepkgs仓库中(建仓流程详见[rpm包构建及建仓流程](https://gitee.com/openeuler/oec-application/blob/master/doc/software-compatibility/rpm%E6%9E%84%E5%BB%BA%E4%BB%A5%E5%8F%8A%E5%BB%BA%E4%BB%93%E6%B5%81%E7%A8%8B.md))** -### 1. 软件包spec及源码文件获取 +>**4. 使用src-oepkgs社区构建工程** -**1.1 在一些网站上找到软件包的src.rpm包,解压获取spec文件以及软件包的源码文件:** +#### 1. 初始化RPM编译环境 +执行命令,安装构建工具: ``` -https://pkgs.org/ -https://src.fedoraproject.org/projects/rpms/* -https://koji.fedoraproject.org/koji/packages -www.google.com -www.baidu.com -www.bing.com +yum install -y dnf-plugins-core rpm-build ``` -以libvirt 4.5.0版本引入为例,在网上寻找src.rpm包的流程如下图所示: -![输入图片说明](./software-compatibility/dist/image.png) -![输入图片说明](./software-compatibility/dist/image2image.png) +生成目录结构: ``` -rpm -i http://vault.centos.org/7.9.2009/os/Source/SPackages/libvirt-4.5.0-36.el7.src.rpm +# 输入任意 **.spec,这一步报错,此时将自动生成目录 +rpmbuild -ba nginx.spec +error: failed to stat /root/nginx.spec: No such file or directory +# 查看自动生成的目录结构 +ls ~/rpmbuild/ +BUILD BUILDROOT RPMS SOURCES SPECS SRPMS ``` -![输入图片说明](./software-compatibility/dist/image3image.png) -![输入图片说明](./software-compatibility/dist/image4image.png) -如上图所示的```~/rpmbuild/SPECS/``` 和 ```~/rpmbuild/SOURCES/```目录下面分别存放了软件包的spec文件以及软件包的源码文件 - -### 2. 在openEuler上进行编译构建: -执行命令,安装构建工具: +准备软件源码到SOURCES ``` -yum install -y dnf-plugins-core rpm-build +wget http://nginx.org/download/nginx-1.20.1.tar.gz +cp nginx-1.20.1.tar.gz ~/rpmbuild/SOURCES/ +``` +创建修改SPEC配置文件 +``` +编写后缀为.spec的文件 +vim ~/rpmbuild/SPECS/nginx.spec +Name: nginx +Version: 1.20.1 +Release: 10 +Summary: Nginx is a web server. +License: GPL +Group: Productivity/Networking/Web/Proxy +URL: test.rpm.com +Source0: nginx-1.20.1.tar.gz +BuildRequires: gcc +BuildRequires: pcre2-devel +BuildRequires: pcre-devel +BuildRequires: openssl-devel +BuildRequires: gdb-headless + +%description +Building a nginx-1.20.1.rpm from nginx-1.20.1.tar.gz + +%post +useradd nginx + +%prep +%setup -q + +%build +./configure +make %{?_smp_mflags} + +%install +make install DESTDIR=%{buildroot} + +%files +%doc +/usr/local/nginx/* + +%changelog +* Sat Dec 06 2022 liping - 1.20.1 - 10 +- Release Nginx 1.20.1 ``` +#### 2. 在openEuler上进行编译构建: 执行命令,安装软件包的依赖包 ``` # yum-builddep -y ~/rpmbuild/SPECS/*.spec -yum-builddep -y ~/rpmbuild/SPECS/libvirt.spec +yum-builddep -y ~/rpmbuild/SPECS/nginx.spec ``` 执行命令,对软件包进行编译构建 ``` # rpmbuild -ba ~/rpmbuild/SPECS/*.spec -rpmbuild -ba ~/rpmbuild/SPECS/libvirt.spec +rpmbuild -ba ~/rpmbuild/SPECS/nginx.spec ``` 编译构建通过就会在 ~/rpmbuild/RPMS/ 目录下面生成 rpm包 ``` ls ~/rpmbuild/RPMS/* ``` -### 3. 在openEuler上进行兼容性测试 +#### 3. 在openEuler上进行兼容性测试 执行命令,测试软件包的安装、卸载 ``` yum localinstall ~/rpmbuild/RPMS/x86_64/* @@ -63,9 +103,17 @@ yum remove * systemctl start * systemctl stop * ``` -### 4. 将已经适配好的软件包的spec文件以及~/rpmbuild/SOURCE目录下面的源码文件存放在src-oepkgs仓库中(建仓流程详见[rpm包构建及建仓流程](https://gitee.com/openeuler/oec-application/blob/master/doc/software-compatibility/rpm%E6%9E%84%E5%BB%BA%E4%BB%A5%E5%8F%8A%E5%BB%BA%E4%BB%93%E6%B5%81%E7%A8%8B.md)) +#### 4. 使用src-oepkgs构建工程完成软件包上传 + +将软件包的spec文件以及~/rpmbuild/SOURCE目录下面的源码文件存放在src-oepkgs仓库中 + +建仓流程详见[rpm包构建及建仓流程](https://gitee.com/openeuler/oec-application/blob/master/doc/software-compatibility/rpm%E6%9E%84%E5%BB%BA%E4%BB%A5%E5%8F%8A%E5%BB%BA%E4%BB%93%E6%B5%81%E7%A8%8B.md)) + + +### 开源软件引入oepkgs个人仓总体流程 + +build.dev.oepkgs.net 构建总体流程 -## build.oepkgs.net 构建总体流程 >**1. 创建个人软件包仓库** >**2. 创建并提交构建任务** @@ -73,14 +121,14 @@ systemctl stop * >**3. 查看并分析构建日志** >**4. 在个人仓库中下载使用软件包** -### 1. 创建个人软件包仓库 +#### 1. 创建个人软件包仓库 在rpm包构建之前,我们可以先选择一个已有的软件包仓库地址或新增一个软件包仓库地址去存放我们待构建的软件包。 切换到构建页面,选择 RPM构建 ---> 仓库管理 ---> 新增仓库 ![输入图片说明](./software-compatibility/dist/storageimage.png) -### 2. 新建一个构建任务 +#### 2. 新建一个构建任务 通过提交构建任务,编译构建出软件包,并发布到上一步骤创建的仓库中。 ![输入图片说明](./software-compatibility/dist/buildtask.png) @@ -95,7 +143,7 @@ systemctl stop * ![输入图片说明](./software-compatibility/dist/8d91fc9db3681d3b367febf82cb83ee.png) -### 3. 查看构建日志 +#### 3. 查看构建日志 ![输入图片说明](./software-compatibility/dist/0474384023b26c8481351fc61236064.png) ![输入图片说明](./software-compatibility/dist/76ddf9290d4b2a2699f9e924d91e735.png) -- Gitee
(.*?)