Compare commits

...

3 Commits

Author SHA1 Message Date
Xuwznln
4529af1fcd 使用rattler-build进行构建测试 2025-08-01 15:06:44 +08:00
Xuwznln
b6a23ef120 更新环境变量级别 2025-08-01 12:41:36 +08:00
Xuwznln
fab90f0b46 更新启动逻辑 2025-08-01 12:12:30 +08:00
11 changed files with 291 additions and 189 deletions

View File

@@ -2,16 +2,21 @@ name: Multi-Platform Conda Build
on:
push:
branches: [ main, dev ]
tags: [ 'v*' ]
branches: [main, dev]
tags: ['v*']
pull_request:
branches: [ main, dev ]
branches: [main, dev]
workflow_dispatch:
inputs:
platforms:
description: '选择构建平台 (逗号分隔): linux-64, osx-64, osx-arm64, win-64'
required: false
default: 'osx-arm64'
upload_to_anaconda:
description: '是否上传到Anaconda.org'
required: false
default: false
type: boolean
jobs:
build:
@@ -19,18 +24,18 @@ jobs:
fail-fast: false
matrix:
include:
- os: ubuntu-latest
platform: linux-64
env_file: unilabos-linux-64.yaml
- os: macos-13 # Intel
platform: osx-64
env_file: unilabos-osx-64.yaml
- os: macos-latest # ARM64
platform: osx-arm64
env_file: unilabos-osx-arm64.yaml
- os: windows-latest
platform: win-64
env_file: unilabos-win64.yaml
- os: ubuntu-latest
platform: linux-64
env_file: unilabos-linux-64.yaml
- os: macos-13 # Intel
platform: osx-64
env_file: unilabos-osx-64.yaml
- os: macos-latest # ARM64
platform: osx-arm64
env_file: unilabos-osx-arm64.yaml
- os: windows-latest
platform: win-64
env_file: unilabos-win64.yaml
runs-on: ${{ matrix.os }}
@@ -39,94 +44,112 @@ jobs:
shell: bash -l {0}
steps:
- uses: actions/checkout@v4
with:
fetch-depth: 0
- uses: actions/checkout@v4
with:
fetch-depth: 0
- name: Check if platform should be built
id: should_build
run: |
if [[ "${{ github.event_name }}" != "workflow_dispatch" ]]; then
echo "should_build=true" >> $GITHUB_OUTPUT
elif [[ -z "${{ github.event.inputs.platforms }}" ]]; then
echo "should_build=true" >> $GITHUB_OUTPUT
elif [[ "${{ github.event.inputs.platforms }}" == *"${{ matrix.platform }}"* ]]; then
echo "should_build=true" >> $GITHUB_OUTPUT
else
echo "should_build=false" >> $GITHUB_OUTPUT
fi
- name: Check if platform should be built
id: should_build
run: |
if [[ "${{ github.event_name }}" != "workflow_dispatch" ]]; then
echo "should_build=true" >> $GITHUB_OUTPUT
elif [[ -z "${{ github.event.inputs.platforms }}" ]]; then
echo "should_build=true" >> $GITHUB_OUTPUT
elif [[ "${{ github.event.inputs.platforms }}" == *"${{ matrix.platform }}"* ]]; then
echo "should_build=true" >> $GITHUB_OUTPUT
else
echo "should_build=false" >> $GITHUB_OUTPUT
fi
- name: Setup Miniconda
if: steps.should_build.outputs.should_build == 'true'
uses: conda-incubator/setup-miniconda@v3
with:
miniconda-version: "latest"
channels: conda-forge,robostack-staging,defaults
channel-priority: strict
activate-environment: build-env
auto-activate-base: false
auto-update-conda: false
show-channel-urls: true
- name: Setup Miniconda
if: steps.should_build.outputs.should_build == 'true'
uses: conda-incubator/setup-miniconda@v3
with:
miniconda-version: 'latest'
channels: conda-forge,robostack-staging,defaults
channel-priority: strict
activate-environment: build-env
auto-activate-base: false
auto-update-conda: false
show-channel-urls: true
- name: Install boa and build tools
if: steps.should_build.outputs.should_build == 'true'
run: |
conda install -c conda-forge boa conda-build
- name: Install rattler-build and anaconda-client
if: steps.should_build.outputs.should_build == 'true'
run: |
conda install -c conda-forge rattler-build anaconda-client
- name: Show environment info
if: steps.should_build.outputs.should_build == 'true'
run: |
conda info
conda list | grep -E "(boa|conda-build)"
echo "Platform: ${{ matrix.platform }}"
echo "OS: ${{ matrix.os }}"
- name: Show environment info
if: steps.should_build.outputs.should_build == 'true'
run: |
conda info
conda list | grep -E "(rattler-build|anaconda-client)"
echo "Platform: ${{ matrix.platform }}"
echo "OS: ${{ matrix.os }}"
- name: Build conda package
if: steps.should_build.outputs.should_build == 'true'
run: |
if [[ "${{ matrix.platform }}" == "osx-arm64" ]]; then
boa build -m ./recipes/conda_build_config.yaml -m ./recipes/macos_sdk_config.yaml ./recipes/ros-humble-unilabos-msgs
else
boa build -m ./recipes/conda_build_config.yaml ./recipes/ros-humble-unilabos-msgs
fi
- name: Build conda package
if: steps.should_build.outputs.should_build == 'true'
run: |
if [[ "${{ matrix.platform }}" == "osx-arm64" ]]; then
rattler-build build -m ./recipes/conda_build_config.yaml -m ./recipes/macos_sdk_config.yaml ./recipes/msgs
else
rattler-build build -m ./recipes/conda_build_config.yaml ./recipes/msgs
fi
- name: List built packages
if: steps.should_build.outputs.should_build == 'true'
run: |
echo "Built packages in conda-bld:"
find $CONDA_PREFIX/conda-bld -name "*.tar.bz2" | head -10
ls -la $CONDA_PREFIX/conda-bld/${{ matrix.platform }}/ || echo "${{ matrix.platform }} directory not found"
ls -la $CONDA_PREFIX/conda-bld/noarch/ || echo "noarch directory not found"
echo "CONDA_PREFIX: $CONDA_PREFIX"
echo "Full path would be: $CONDA_PREFIX/conda-bld/**/*.tar.bz2"
- name: List built packages
if: steps.should_build.outputs.should_build == 'true'
run: |
echo "Built packages in output directory:"
find ./recipes/msgs/output -name "*.conda" | head -10
ls -la ./recipes/msgs/output/${{ matrix.platform }}/ || echo "${{ matrix.platform }} directory not found"
ls -la ./recipes/msgs/output/noarch/ || echo "noarch directory not found"
echo "Output directory structure:"
find ./recipes/msgs/output -type f -name "*.conda"
- name: Prepare artifacts for upload
if: steps.should_build.outputs.should_build == 'true'
run: |
mkdir -p ${{ runner.temp }}/conda-packages
find $CONDA_PREFIX/conda-bld -name "*.tar.bz2" -exec cp {} ${{ runner.temp }}/conda-packages/ \;
echo "Copied files to temp directory:"
ls -la ${{ runner.temp }}/conda-packages/
- name: Prepare artifacts for upload
if: steps.should_build.outputs.should_build == 'true'
run: |
mkdir -p ${{ runner.temp }}/conda-packages
find ./recipes/msgs/output -name "*.conda" -exec cp {} ${{ runner.temp }}/conda-packages/ \;
echo "Copied files to temp directory:"
ls -la ${{ runner.temp }}/conda-packages/
- name: Upload conda package artifacts
if: steps.should_build.outputs.should_build == 'true'
uses: actions/upload-artifact@v4
with:
name: conda-package-${{ matrix.platform }}
path: ${{ runner.temp }}/conda-packages
if-no-files-found: warn
retention-days: 30
- name: Upload conda package artifacts
if: steps.should_build.outputs.should_build == 'true'
uses: actions/upload-artifact@v4
with:
name: conda-package-${{ matrix.platform }}
path: ${{ runner.temp }}/conda-packages
if-no-files-found: warn
retention-days: 30
- name: Create release assets (on tags)
if: steps.should_build.outputs.should_build == 'true' && startsWith(github.ref, 'refs/tags/')
run: |
mkdir -p release-assets
find $CONDA_PREFIX/conda-bld -name "*.tar.bz2" -exec cp {} release-assets/ \;
- name: Login to Anaconda
if: steps.should_build.outputs.should_build == 'true' && (github.event.inputs.upload_to_anaconda == 'true' || startsWith(github.ref, 'refs/tags/'))
run: |
anaconda login --username ${{ secrets.ANACONDA_USERNAME }} --password ${{ secrets.ANACONDA_PASSWORD }}
- name: Upload to release
if: steps.should_build.outputs.should_build == 'true' && startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v1
with:
files: release-assets/*
draft: false
prerelease: false
- name: Upload to Anaconda.org
if: steps.should_build.outputs.should_build == 'true' && (github.event.inputs.upload_to_anaconda == 'true' || startsWith(github.ref, 'refs/tags/'))
run: |
for package in $(find ./recipes/msgs/output -name "*.conda"); do
echo "Uploading $package to Anaconda.org..."
anaconda upload --user ${{ secrets.ANACONDA_ORG }} --force "$package"
done
- name: Logout from Anaconda
if: always() && steps.should_build.outputs.should_build == 'true' && (github.event.inputs.upload_to_anaconda == 'true' || startsWith(github.ref, 'refs/tags/'))
run: |
anaconda logout || true
- name: Create release assets (on tags)
if: steps.should_build.outputs.should_build == 'true' && startsWith(github.ref, 'refs/tags/')
run: |
mkdir -p release-assets
find ./recipes/msgs/output -name "*.conda" -exec cp {} release-assets/ \;
- name: Upload to release
if: steps.should_build.outputs.should_build == 'true' && startsWith(github.ref, 'refs/tags/')
uses: softprops/action-gh-release@v1
with:
files: release-assets/*
draft: false
prerelease: false

View File

@@ -34,8 +34,32 @@ cmake ^
-DCMAKE_OBJECT_PATH_MAX=255 ^
-DPYTHON_INSTALL_DIR=%SP_DIR_FORWARDSLASHES% ^
--compile-no-warning-as-error ^
%SRC_DIR%\%PKG_NAME%\src\work
%SRC_DIR%\src
if errorlevel 1 exit 1
set "infile=%SRC_DIR%\build\cmake_install.cmake"
set "tmpfile=%infile%.tmp"
rem 模式串
set "pattern=echo %PYTHON%"
> "%tmpfile%" (
for /f "usebackq delims=" %%L in ("%infile%") do (
set "line=%%L"
rem 把 pattern 从 line 里“删除”,看看结果是否变化
set "test=!line:%pattern%=!"
if "!test!" neq "!line!" (
rem 含有 echo %PYTHON%,对整行做 \→\\ 全局替换
echo !line:\=\\!
) else (
rem 不含,原样输出
echo !line!
)
)
)
rem 用 tmp 覆盖原文件
move /Y "%tmpfile%" "%infile%" > nul
cmake --build . --config Release --target install
if errorlevel 1 exit 1

View File

@@ -66,6 +66,6 @@ cmake \
-DBUILD_TESTING=OFF \
-DCMAKE_OSX_DEPLOYMENT_TARGET=$OSX_DEPLOYMENT_TARGET \
--compile-no-warning-as-error \
$SRC_DIR/$PKG_NAME/src/work
$SRC_DIR/src
cmake --build . --config Release --target install

76
recipes/msgs/recipe.yaml Normal file
View File

@@ -0,0 +1,76 @@
package:
name: ros-humble-unilabos-msgs
version: 0.10.1
source:
path: ../../unilabos_msgs
target_directory: src
build:
script:
- if: win
then:
- copy %RECIPE_DIR%\bld_ament_cmake.bat %SRC_DIR%
- call %SRC_DIR%\bld_ament_cmake.bat
- if: unix
then:
- cp $RECIPE_DIR/build_ament_cmake.sh $SRC_DIR
- bash $SRC_DIR/build_ament_cmake.sh
about:
repository: https://github.com/dptech-corp/Uni-Lab-OS
license: BSD-3-Clause
description: "ros-humble-unilabos-msgs is a package that provides message definitions for Uni-Lab-OS."
requirements:
build:
- if: build_platform != target_platform
then:
- pkg-config
- python =3.11.11
- cross-python_${{ target_platform }}
- numpy
- ${{ compiler('cxx') }}
- ${{ compiler('c') }}
- if: linux and x86_64
then:
- sysroot_linux-64 2.17
- ninja
- setuptools
- cython
- cmake
- if: unix
then:
- make
- coreutils
- if: osx
then:
- tapi
- if: win
then:
- vs2022_win-64
host:
- numpy
- pip
- if: build_platform == target_platform
then:
- pkg-config
- robostack-staging::ros-humble-action-msgs
- robostack-staging::ros-humble-ament-cmake
- robostack-staging::ros-humble-ament-lint-auto
- robostack-staging::ros-humble-ament-lint-common
- robostack-staging::ros-humble-ros-environment
- robostack-staging::ros-humble-ros-workspace
- robostack-staging::ros-humble-rosidl-default-generators
- robostack-staging::ros-humble-std-msgs
- robostack-staging::ros-humble-geometry-msgs
- robostack-staging::ros2-distro-mutex=0.6
run:
- robostack-staging::ros-humble-action-msgs
- robostack-staging::ros-humble-ros-workspace
- robostack-staging::ros-humble-rosidl-default-runtime
- robostack-staging::ros-humble-std-msgs
- robostack-staging::ros-humble-geometry-msgs
- robostack-staging::ros2-distro-mutex=0.6
- if: osx and x86_64
then:
- __osx >= {{ MACOSX_DEPLOYMENT_TARGET|default('10.14') }}

View File

@@ -1,61 +0,0 @@
package:
name: ros-humble-unilabos-msgs
version: 0.10.1
source:
path: ../../unilabos_msgs
folder: ros-humble-unilabos-msgs/src/work
build:
script:
sel(win): bld_ament_cmake.bat
sel(unix): build_ament_cmake.sh
number: 5
about:
home: https://www.ros.org/
license: BSD-3-Clause
summary: |
Robot Operating System
extra:
recipe-maintainers:
- ros-forge
requirements:
build:
- "{{ compiler('cxx') }}"
- "{{ compiler('c') }}"
- sel(linux64): sysroot_linux-64 2.17
- ninja
- setuptools
- sel(unix): make
- sel(unix): coreutils
- sel(osx): tapi
- sel(build_platform != target_platform): pkg-config
- cmake
- cython
- sel(win): vs2022_win-64
- sel(build_platform != target_platform): python
- sel(build_platform != target_platform): cross-python_{{ target_platform }}
- sel(build_platform != target_platform): numpy
host:
- numpy
- pip
- sel(build_platform == target_platform): pkg-config
- robostack-staging::ros-humble-action-msgs
- robostack-staging::ros-humble-ament-cmake
- robostack-staging::ros-humble-ament-lint-auto
- robostack-staging::ros-humble-ament-lint-common
- robostack-staging::ros-humble-ros-environment
- robostack-staging::ros-humble-ros-workspace
- robostack-staging::ros-humble-rosidl-default-generators
- robostack-staging::ros-humble-std-msgs
- robostack-staging::ros-humble-geometry-msgs
- robostack-staging::ros2-distro-mutex=0.6.*
run:
- robostack-staging::ros-humble-action-msgs
- robostack-staging::ros-humble-ros-workspace
- robostack-staging::ros-humble-rosidl-default-runtime
- robostack-staging::ros-humble-std-msgs
- robostack-staging::ros-humble-geometry-msgs
# - robostack-staging::ros2-distro-mutex=0.6.*
- sel(osx and x86_64): __osx >={{ MACOSX_DEPLOYMENT_TARGET|default('10.14') }}

View File

@@ -43,10 +43,11 @@ def convert_argv_dashes_to_underscores(args: argparse.ArgumentParser):
for i, arg in enumerate(sys.argv):
for option_string in option_strings:
if arg.startswith(option_string):
new_arg = arg[:2] + arg[2:len(option_string)].replace("-", "_") + arg[len(option_string):]
new_arg = arg[:2] + arg[2 : len(option_string)].replace("-", "_") + arg[len(option_string) :]
sys.argv[i] = new_arg
break
def parse_args():
"""解析命令行参数"""
parser = argparse.ArgumentParser(description="Start Uni-Lab Edge server.")
@@ -128,6 +129,13 @@ def parse_args():
default="",
help="实验室唯一ID也可通过环境变量 UNILABOS.MQCONFIG.LABID 设置或传入--config设置",
)
parser.add_argument(
"--loglevel",
type=str,
choices=["DEBUG", "INFO", "WARNING", "ERROR", "CRITICAL"],
default="INFO",
help="设置日志级别 (DEBUG, INFO, WARNING, ERROR, CRITICAL),默认为 INFO",
)
return parser
@@ -140,29 +148,40 @@ def main():
# 加载配置文件优先加载config然后从env读取
config_path = args_dict.get("config")
working_dir = os.path.abspath(os.path.join(os.getcwd(), "unilabos_data"))
if not config_path and (not os.path.exists(working_dir) or not os.path.exists(os.path.join(working_dir, "local_config.py"))):
print_status(f"当前未指定config路径非第一次使用请通过 --config 传入 local_config.py 文件路径", "info")
print_status(f"您是否为第一次使用?并将当前文件路径 {working_dir} 作为工作目录? (Y/n)", "info")
if os.getcwd().endswith("unilabos_data"):
working_dir = os.path.abspath(os.getcwd())
else:
working_dir = os.path.abspath(os.path.join(os.getcwd(), "unilabos_data"))
if args_dict.get("working_dir"):
working_dir = args_dict.get("working_dir")
if config_path and not os.path.exists(config_path):
config_path = os.path.join(working_dir, "local_config.py")
if not os.path.exists(config_path):
print_status(
f"当前工作目录 {working_dir} 未找到local_config.py请通过 --config 传入 local_config.py 文件路径",
"error",
)
os._exit(1)
elif os.path.exists(working_dir) and os.path.exists(os.path.join(working_dir, "local_config.py")):
config_path = os.path.join(working_dir, "local_config.py")
elif not config_path and (
not os.path.exists(working_dir) or not os.path.exists(os.path.join(working_dir, "local_config.py"))
):
print_status(f"未指定config路径可通过 --config 传入 local_config.py 文件路径", "info")
print_status(f"您是否为第一次使用?并将当前路径 {working_dir} 作为工作目录? (Y/n)", "info")
if input() != "n":
os.makedirs(working_dir, exist_ok=True)
config_path = os.path.join(working_dir, "local_config.py")
shutil.copy(os.path.join(os.path.dirname(os.path.dirname(__file__)), "config", "example_config.py"), config_path)
shutil.copy(
os.path.join(os.path.dirname(os.path.dirname(__file__)), "config", "example_config.py"), config_path
)
print_status(f"已创建 local_config.py 路径: {config_path}", "info")
print_status(f"请在文件夹中配置lab_id放入下载的CA.crt、lab.crt、lab.key重新启动本程序", "info")
os._exit(1)
else:
os._exit(1)
else:
working_dir = args_dict.get("working_dir") or os.path.abspath(os.path.join(os.getcwd(), "unilabos_data"))
if working_dir:
if config_path and not os.path.exists(config_path):
config_path = os.path.join(working_dir, "local_config.py")
if not os.path.exists(config_path):
print_status(f"当前工作目录 {working_dir} 未找到local_config.py请通过 --config 传入 local_config.py 文件路径", "error")
os._exit(1)
print_status(f"当前工作目录为 {working_dir}", "info")
# 加载配置文件
print_status(f"当前工作目录为 {working_dir}", "info")
load_config_from_file(config_path, args_dict["labid"])
# 设置BasicConfig参数
@@ -204,10 +223,11 @@ def main():
print_status("联网获取设备加载文件成功", "info")
graph, data = read_node_link_json(request_startup_json)
else:
if args_dict["graph"].endswith(".json"):
graph, data = read_node_link_json(args_dict["graph"])
file_path = args_dict["graph"]
if file_path.endswith(".json"):
graph, data = read_node_link_json(file_path)
else:
graph, data = read_graphml(args_dict["graph"])
graph, data = read_graphml(file_path)
import unilabos.resources.graphio as graph_res
graph_res.physical_setup_graph = graph

View File

@@ -4,11 +4,12 @@ HTTP客户端模块
提供与远程服务器通信的客户端功能只有host需要用
"""
import json
import os
from typing import List, Dict, Any, Optional
import requests
from unilabos.utils.log import info
from unilabos.config.config import MQConfig, HTTPConfig
from unilabos.config.config import MQConfig, HTTPConfig, BasicConfig
from unilabos.utils import logger
@@ -189,7 +190,7 @@ class HTTPClient:
logger.error(f"请求启动配置失败: {response.status_code}, {response.text}")
else:
try:
with open("startup_config.json", "w", encoding="utf-8") as f:
with open(os.path.join(BasicConfig.working_dir, "startup_config.json"), "w", encoding="utf-8") as f:
f.write(response.text)
target_dict = json.loads(response.text)
if "data" in target_dict:

View File

@@ -163,12 +163,12 @@ def _update_config_from_env():
def load_config(config_path=None, override_labid=None):
# 如果提供了配置文件路径,从该文件导入配置
if config_path:
_update_config_from_env() # 允许config_path被env设定后读取
env_config_path = os.environ.get("UNILABOS.BASICCONFIG.CONFIG_PATH")
config_path = env_config_path if env_config_path else config_path
BasicConfig.config_path = os.path.abspath(os.path.dirname(config_path))
if not os.path.exists(config_path):
logger.error(f"[ENV] 配置文件 {config_path} 不存在")
exit(1)
try:
module_name = "lab_" + os.path.basename(config_path).replace(".py", "")
spec = importlib.util.spec_from_file_location(module_name, config_path)
@@ -179,6 +179,7 @@ def load_config(config_path=None, override_labid=None):
spec.loader.exec_module(module) # type: ignore
_update_config_from_module(module, override_labid)
logger.info(f"[ENV] 配置文件 {config_path} 加载成功")
_update_config_from_env()
except Exception as e:
logger.error(f"[ENV] 加载配置文件 {config_path} 失败")
traceback.print_exc()

View File

@@ -8,9 +8,9 @@ class MQConfig:
broker_url = ""
port = 1883
ca_file = "CA.crt"
cert_file = "lab.crt"
key_file = "lab.key"
ca_file = "./CA.crt"
cert_file = "./lab.crt"
key_file = "./lab.key"
# HTTP配置
class HTTPConfig:

View File

@@ -144,11 +144,29 @@ class ColoredFormatter(logging.Formatter):
# 配置日志处理器
def configure_logger():
"""配置日志记录器"""
def configure_logger(loglevel=None):
"""配置日志记录器
Args:
loglevel: 日志级别,可以是字符串('DEBUG', 'INFO', 'WARNING', 'ERROR', 'CRITICAL'
或logging模块的常量如logging.DEBUG
"""
# 获取根日志记录器
root_logger = logging.getLogger()
root_logger.setLevel(logging.DEBUG) # 修改为DEBUG以显示所有级别
# 设置日志级别
if loglevel is not None:
if isinstance(loglevel, str):
# 将字符串转换为logging级别
numeric_level = getattr(logging, loglevel.upper(), None)
if not isinstance(numeric_level, int):
print(f"警告: 无效的日志级别 '{loglevel}',使用默认级别 DEBUG")
numeric_level = logging.DEBUG
else:
numeric_level = loglevel
root_logger.setLevel(numeric_level)
else:
root_logger.setLevel(logging.DEBUG) # 默认级别
# 移除已存在的处理器
for handler in root_logger.handlers[:]:
@@ -156,7 +174,7 @@ def configure_logger():
# 创建控制台处理器
console_handler = logging.StreamHandler()
console_handler.setLevel(logging.DEBUG) # 修改为DEBUG以显示所有级别
console_handler.setLevel(root_logger.level) # 使用与根记录器相同的级别
# 使用自定义的颜色格式化器
color_formatter = ColoredFormatter()

View File

@@ -2,7 +2,7 @@
<?xml-model href="http://download.ros.org/schema/package_format3.xsd" schematypens="http://www.w3.org/2001/XMLSchema"?>
<package format="3">
<name>unilabos_msgs</name>
<version>0.0.5</version>
<version>0.10.1</version>
<description>ROS2 Messages package for unilabos devices</description>
<maintainer email="changjh@pku.edu.cn">Junhan Chang</maintainer>
<license>MIT</license>