包管理与模块

18 种语言的包管理与模块对照:模块与命名空间、依赖声明与锁定、包仓库与发布、版本约束与常见坑

包管理与模块:18 种语言对照

“包管理"要回答四个问题:代码怎么分组(模块/命名空间)、依赖怎么声明与锁定(清单 + 锁文件)、包怎么发布与获取(注册中心)、版本怎么解析(语义化版本与冲突处理)。本页按这四个主题组织,每个主题一套 18 语言标签页;命令与文件名都给可直接照抄的写法。

包管理与模块

一页速览

语言模块单位包管理器清单 / 锁文件注册中心
Rustcrate + modCargoCargo.toml / Cargo.lockcrates.io
Swifttarget(module)SwiftPMPackage.swift / Package.resolvedGit 仓库(无中心仓库)
Gopackage(目录)go 命令go.mod / go.sum无(模块 = Git 仓库)
Pythonmodule + packagepip / uv / poetrypyproject.toml / uv.lock、poetry.lockPyPI
KotlinpackageGradle / Mavenbuild.gradle.kts / 无默认锁Maven Central
Javapackage(+ JPMS module)Maven / Gradlepom.xml / 无默认锁(用 BOM)Maven Central
C++namespace + 头文件/模块vcpkg / Conanvcpkg.json / vcpkg-lock.json无官方(vcpkg、Conan Center)
C翻译单元 + 头文件系统包管理器无无(发行版 / 源码)
JuliamodulePkgProject.toml / Manifest.tomlGeneral Registry
C#namespace + assemblyNuGet.csproj / packages.lock.json(可选)NuGet.org
Dartlibrarypubpubspec.yaml / pubspec.lockpub.dev
RpackageR CMD + renvDESCRIPTION / renv.lockCRAN、Bioconductor
Zig文件即模块zig buildbuild.zig.zon(哈希即锁)无(URL + 哈希)
Lua文件 + tableLuaRocks.rockspec / 无锁luarocks.org
TypeScriptES modulenpm / pnpm / yarnpackage.json / lock 文件npm Registry
JavaScriptESM / CommonJSnpm / pnpm / yarnpackage.json / lock 文件npm Registry
PHPnamespaceComposercomposer.json / composer.lockPackagist
Rubymodule / classBundler + RubyGemsGemfile / Gemfile.lockRubyGems.org

模块与命名空间

模块树由 mod 组成,crate 才是编译单元(bin 或 lib);文件即模块(mod foo; 对应 foo.rs 或 foo/mod.rs)。

1
2
3
4
5
6
// src/lib.rs
pub mod parser;          // 引入 src/parser.rs
pub(crate) mod internal;  // 只在当前 crate 可见

use crate::parser::parse;
use std::collections::HashMap;

可见性有 pub、pub(crate)、pub(super)、pub(in path) 四档;crate:: 从根开始、super:: 上一级、self:: 当前模块。

📘 Rust Reference · Modules

模块 = SwiftPM 的 target(或 Xcode 的 framework/library target);默认可见性是 internal(同模块可见),跨模块要用 public/open。

1
2
3
4
5
6
// Package.swift 中的 target 就是模块
// Sources/Parser/Parse.swift
public struct Parser {          // 跨模块可见
    public init() {}
    internal func helper() {}     // 同模块可见
}
1
2
import Parser                    // 引入另一个模块
@testable import Parser           // 测试里可见 internal 成员

Swift 没有"命名空间"关键字:模块名 + 类型名就是天然的命名空间;@testable 是测试专用后门。

📘 SwiftPM · 模块

Go 里一个目录就是一个 package,import 路径就是模块路径 + 目录;导出与否只看首字母大小写。

1
2
3
4
5
// 文件头声明所属 package
package parser

func Parse(s string) (int, error) { ... }   // 导出(大写)
func helper() {}                             // 未导出(小写)
1
2
3
4
5
6
import (
    "example.com/demo/parser"   // 自己模块里的包
    "github.com/pkg/errors"
)

p, err := parser.Parse("42")

internal/ 目录下的包只能被同一模块内部引用(编译器强制);go mod init 里的模块路径就是 import 前缀。

📘 Go · 包

module 是一个 .py 文件,package 是带 __init__.py 的目录(PEP 420 的"命名空间包"可以没有)。

1
2
3
4
5
6
# mypkg/__init__.py
from .parser import parse          # 相对导入
__all__ = ["parse"]                 # 控制 from mypkg import * 的内容

# mypkg/parser.py
def parse(s: str) -> int: return int(s)
1
2
import mypkg.parser as p           # 绝对导入(推荐)
from mypkg import parse

PEP 8 建议优先用绝对导入;相对导入(.parser)只在包内部使用,且不能用于顶层脚本。

📘 Python · 模块

package 是逻辑命名空间,不要求目录结构一致;internal 表示"模块内可见"(Gradle 模块/编译单元)。

1
2
3
4
package com.example.parser

internal fun parse(s: String): Int = s.toInt()   // 模块内可见
fun publicApi() {}
1
import com.example.parser.publicApi

Kotlin 的可见性只有 public/internal/protected/private 四档;internal 的边界是"同一个编译模块"(Gradle 里通常是一个 module)。

📘 Kotlin · 可见性

package 与目录结构一致;Java 9 起还有 JPMS 模块(module-info.java),用 requires/exports 精确声明依赖与导出。

1
2
3
4
5
// module-info.java
module com.example.demo {
    requires java.sql;
    exports com.example.demo.api;      // 只有这个包对外可见
}
1
2
package com.example.demo.api;
import java.util.List;                  // 普通 import

普通项目只用 package + classpath 就够了;JPMS 的价值在于"显式依赖 + 强封装",代价是需要额外的模块声明与迁移工作。

📘 JLS · 包

namespace 是逻辑分组;物理分组靠头文件 + 源文件,C++20 起还有真正的模块(import/export module)。

1
2
3
4
5
// 头文件方式
namespace demo { int parse(const std::string&); }

inline namespace v1 { }        // 版本化命名空间(ABI 技巧)
namespace { int helper(); }     // 匿名命名空间:仅本翻译单元可见
1
2
3
// C++20 模块
export module demo.parser;
export int parse(const std::string&);

头文件 + #include 仍是主流(模块支持取决于工具链);匿名命名空间是"文件级私有"的标准做法。

📘 cppreference · Namespaces、Modules

C 没有模块与命名空间:代码分组靠"头文件 + 翻译单元",可见性靠 static(内部链接)与命名前缀(如 demo_parse)。

1
2
3
4
5
6
7
8
9
/* demo_parser.h */
#ifndef DEMO_PARSER_H
#define DEMO_PARSER_H
int demo_parse(const char *s);
#endif

/* demo_parser.c */
static int helper(void) { return 0; }   /* 仅本文件可见 */
int demo_parse(const char *s) { return helper(); }

头文件用 include guard(或 #pragma once)防止重复包含;符号冲突全靠前缀约定与 static 控制。

📘 C · 翻译单元

module ... end 是命名空间,using/import 引入包,export 决定 using 后哪些名字直接可见。

 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
module Demo
export parse

parse(s::AbstractString) = tryparse(Int, s)

module Internal          # 子模块
    helper() = 1
end
end

using .Demo               # 相对路径引入当前项目里的模块
using .Demo.Internal

using Demo 会带来导出的名字,import Demo 只引入模块名(要写 Demo.parse);../. 前缀表示相对当前模块。

📘 Julia · 模块

namespace 是逻辑分组,assembly(.csproj)才是物理单元;C# 10 起支持 file-scoped namespace,internal 表示程序集内可见。

1
2
3
4
namespace Demo.Parser;            // file-scoped namespace(C# 10+)

internal sealed class Parser { }   // 程序集内可见
public record Result(int Value);    // 跨程序集可见
1
2
using Demo.Parser;                 // 引入命名空间
global using System.Text.Json;      // global using(C# 10+):整个项目生效

InternalsVisibleTo 可以让测试程序集访问 internal 成员;global using 适合把常用命名空间集中声明一次。

📘 MS Learn · 命名空间

Dart 的可见性单位是库(library)——通常就是一个文件;_ 开头的成员只在库内可见。import/export/part 组织代码。

1
2
3
4
5
// lib/parser.dart
class Parser {
  int _helper() => 0;      // 库级私有
  int parse(String s) => int.parse(s);
}
1
2
3
import 'package:demo/parser.dart' show Parser;   // 只引入部分成员
import 'dart:async' as async;                     // 起别名
export 'src/impl.dart';                           // 转发导出

package: 导入指向 lib/ 下的路径(需要 pubspec.yaml 的 name),相对导入用于包内文件。

📘 Dart · 库与可见性

R 的"模块"就是 package:DESCRIPTION + NAMESPACE 两个文件定义元数据、导出与依赖;pkg::fun 是显式调用。

1
2
3
library(dplyr)            # 挂到搜索路径(可直接用函数名)
require(dplyr)             # 类似,但返回 TRUE/FALSE
dplyr::filter(df, x > 0)    # 显式命名空间(推荐在包/脚本里用)
1
2
3
# NAMESPACE 里由 roxygen2 生成
export(parse)
importFrom(stats, median)

函数名冲突时 :: 是唯一可靠的写法;包内部一律用 pkg::fun 或 @importFrom,避免依赖用户的 library() 顺序。

📘 R · 命名空间

文件即模块:用 @import("file.zig") 引入,pub 决定是否导出;build.zig 里可以把依赖注册成模块。

1
2
3
4
5
6
7
8
9
// src/parser.zig
pub fn parse(s: []const u8) !u32 { return 42; }

// src/main.zig
const parser = @import("parser.zig");
pub fn main() !void {
    const v = try parser.parse("42");
    _ = v;
}
1
2
3
// build.zig 片段:把依赖注册为模块
const dep = b.dependency("zap", .{});
exe.root_module.addImport("zap", dep.module("zap"));

没有命名空间关键字:路径 + pub 就是全部;std 也是一个模块(@import("std"))。

📘 Zig · @import

Lua 没有模块语法:require("mod") 按 package.path 找文件,惯例是文件返回一个 table;module() 函数在 5.2 已被移除。

1
2
3
4
-- parser.lua
local Parser = {}
function Parser.parse(s) return tonumber(s) end
return Parser               -- 返回模块表
1
2
3
4
local parser = require("parser")
print(parser.parse("42"))

print(package.path)         -- 查看搜索路径(LUA_PATH)

模块名与文件名对应(parser → parser.lua 或 parser/init.lua);require 会缓存结果,改代码要重启进程或清 package.loaded。

📘 Lua 5.5 · require

用 ES module 的 import/export(早期还有 namespace,现在主要用来给全局脚本分组);类型声明放在 .d.ts 里。

1
2
3
4
5
// src/parser.ts
export function parse(s: string): number { return Number(s); }
export type Result = { value: number };

import { parse, type Result } from "./parser.js";   // 相对路径 + 扩展名(NodeNext)
1
2
3
4
// 给没有类型的库写声明
declare module "legacy-lib" {
  export function doThing(x: number): string;
}

tsconfig 的 moduleResolution/paths 决定解析规则;monorepo 用 project references 把多个包连起来。

📘 TS · 模块

两套模块系统并存:ESM(import/export)与 CommonJS(require/module.exports);package.json 的 type 与文件扩展名决定用哪套。

1
2
3
4
5
6
7
// ESM(.mjs 或 "type": "module")
import { parse } from "./parser.js";
export function run() {}

// CommonJS(.cjs)
const { parse } = require("./parser.cjs");
module.exports = { run };
1
2
// package.json
{ "type": "module", "exports": { ".": "./dist/index.js" } }

exports 字段决定外部能 import 哪些路径(比 main 更严格);ESM 与 CJS 互操作有诸多限制,混用时要看清 default 包装。

📘 Node.js · 模块

namespace + use 是语言层机制;自动加载靠 Composer 的 PSR-4 映射(命名空间前缀 → 目录)。

1
2
3
4
5
6
7
namespace App\Parser;

use App\Support\Result;

final class Parser {
    public function parse(string $s): Result { return new Result((int) $s); }
}
1
2
// composer.json
{ "autoload": { "psr-4": { "App\\": "src/" } } }

PSR-4 的规则是"前缀对应目录、其余对应路径",所以 App\Parser\Parser 必须在 src/Parser/Parser.php;改完映射要 composer dump-autoload。

📘 PSR-4

module 既是命名空间也是 mixin;文件用 require/require_relative 引入,gem 用 lib/ 目录约定。

1
2
3
4
5
6
7
module Demo
  module Parser
    def self.parse(s) = Integer(s)
  end
end

Demo::Parser.parse("42")        # 用 :: 访问命名空间
1
2
require_relative "parser"        # 相对当前文件(推荐)
require "json"                    # 从 $LOAD_PATH / gem 加载

Rails 用 Zeitwerk 按"文件名 → 常量名"自动加载(app/services/user_mailer.rb → UserMailer),不需要手写 require。

📘 Ruby · 模块

依赖声明与锁定

项目内容
清单Cargo.toml([dependencies])
锁文件Cargo.lock(二进制项目提交,库项目通常不提交)
常用命令cargo add、cargo remove、cargo update、cargo tree
1
2
3
[dependencies]
serde = { version = "1", features = ["derive"] }
tokio = { version = "1.40", features = ["rt-multi-thread", "macros"] }

cargo add 写清单、cargo update 只改锁文件;cargo tree -d 能看出同一个 crate 被解析成了多个版本。

📘 Cargo · 依赖

项目内容
清单Package.swift
锁文件Package.resolved
常用命令swift package resolve、update、show-dependencies
1
2
3
dependencies: [
    .package(url: "https://github.com/apple/swift-argument-parser.git", from: "1.5.0"),
]

SwiftPM 的依赖就是 Git 仓库 + 版本标签;from: 等价于"语义化版本范围",Package.resolved 记录解析结果(应提交)。

📘 SwiftPM · 依赖

项目内容
清单go.mod
锁文件go.sum(校验和)+ go.mod 里的精确版本
常用命令go get、go mod tidy、go mod download、go mod graph
module example.com/demo

go 1.27

require github.com/gin-gonic/gin v1.10.0
1
2
3
go mod tidy        # 补齐缺失、删除未用
go mod graph       # 依赖图
go list -m all     # 所有依赖的最终版本

Go 用最小版本选择(MVS):每个依赖取满足所有要求的最低版本,因此升级要用显式 go get pkg@version。

📘 Go Modules 参考

项目内容
清单pyproject.toml(推荐)、requirements.txt(传统)
锁文件uv.lock、poetry.lock、pdm.lock(pip 无锁文件)
常用命令pip install、uv add/sync、poetry add/install
1
2
3
4
5
6
[project]
name = "demo"
dependencies = ["httpx>=0.27", "pydantic>=2.8"]

[project.optional-dependencies]
dev = ["pytest", "ruff"]
1
2
uv sync              # 按 uv.lock 精确安装
pip install -e ".[dev]"

没有锁文件就无法保证"昨天能装、今天也能装";团队项目建议统一用带锁的工具(uv/poetry/pdm)。

📘 Python 打包、uv

项目内容
清单build.gradle.kts + settings.gradle.kts
锁文件默认无;可开 dependencyLocking
常用命令./gradlew dependencies、build、dependencyUpdates
1
2
3
4
dependencies {
    implementation("org.jetbrains.kotlinx:kotlinx-serialization-json:1.7.3")
    testImplementation(kotlin("test"))
}

版本集中在 gradle/libs.versions.toml(version catalog)里声明,避免散落在各处;gradle wrapper 固定 Gradle 版本。

📘 Gradle · 依赖管理

项目内容
清单pom.xml(Maven)/ build.gradle(.kts)
锁文件无;用 BOM 或 dependencyManagement 固定版本
常用命令mvn dependency:tree、mvn versions:display-dependency-updates
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
<dependencyManagement>
  <dependencies>
    <dependency>
      <groupId>org.junit</groupId>
      <artifactId>junit-bom</artifactId>
      <version>5.11.0</version>
      <type>pom</type><scope>import</scope>
    </dependency>
  </dependencies>
</dependencyManagement>

BOM(导入的 pom)把一组依赖的版本统一起来;dependency:tree 用来排查"某个传递依赖从哪来"。

📘 Maven · 依赖机制

项目内容
清单vcpkg.json(vcpkg)/ conanfile.txt(Conan)
锁文件vcpkg-lock.json、conan.lock(可选)
常用命令vcpkg install、conan install、cmake --preset
1
2
3
// vcpkg.json
{ "name": "demo", "version": "0.1.0",
  "dependencies": ["fmt", "nlohmann-json"] }
1
2
vcpkg install --triplet arm64-osx
cmake -S . -B build -DCMAKE_TOOLCHAIN_FILE=$VCPKG_ROOT/scripts/buildsystems/vcpkg.cmake

没有锁文件时,vcpkg 的"基线(baseline)“就是隐式锁定:builtin-baseline 指向一个 commit,保证所有人拿到同一版本。

📘 vcpkg manifest

项目内容
清单无(依赖系统包管理器)
锁文件无
常用命令pkg-config --cflags --libs、apt/dnf/brew install
1
2
cc main.c $(pkg-config --cflags --libs libcurl) -o app
pkg-config --modversion libcurl

要"自带依赖"就把源码 vendor 进仓库(如 deps/),或用 CMake 的 FetchContent/ExternalProject 在构建时拉取并固定 commit。

📘 pkg-config

项目内容
清单Project.toml
锁文件Manifest.toml(必须提交)
常用命令Pkg.add、Pkg.rm、Pkg.instantiate、Pkg.status
1
2
3
4
using Pkg
Pkg.activate(".")
Pkg.add(["DataFrames", "CSV"])
Pkg.instantiate()          # 按 Manifest.toml 精确还原

Project.toml 记直接依赖与兼容范围,Manifest.toml 记完整依赖树与精确版本;只有两者都在,环境才可复现。

📘 Pkg · Project 与 Manifest

项目内容
清单.csproj(PackageReference)
锁文件packages.lock.json(RestorePackagesWithLockFile)
常用命令dotnet add package、dotnet restore、dotnet list package
1
2
3
4
5
6
<ItemGroup>
  <PackageReference Include="Serilog" Version="4.2.0" />
</ItemGroup>
<PropertyGroup>
  <RestorePackagesWithLockFile>true</RestorePackagesWithLockFile>
</PropertyGroup>
1
dotnet restore --locked-mode     # CI 里强制按锁文件还原

中央包管理(Directory.Packages.props + ManagePackageVersionsCentrally)让整个解决方案的版本集中在一处。

📘 NuGet · 包引用

项目内容
清单pubspec.yaml
锁文件pubspec.lock(应用提交,库通常不提交)
常用命令dart pub get、upgrade、outdated、add
1
2
3
4
5
dependencies:
  http: ^1.2.0
  path: any                 # any:不限制版本(慎用)
dev_dependencies:
  test: ^1.25.0
1
2
dart pub add http            # 自动写入并解析
dart pub upgrade --major-versions

Dart 3.6+ 的 pub workspace 让多个包共享一份 pubspec.lock,monorepo 里更一致。

📘 pubspec

项目内容
清单DESCRIPTION(Imports/Suggests)
锁文件renv.lock
常用命令install.packages、renv::snapshot()、renv::restore()
1
2
3
renv::init()                # 为项目建立环境与 renv.lock
renv::snapshot()             # 记录当前包版本
renv::restore()               # 按锁文件还原

CRAN 上的包随时可能更新,renv 的价值就是把"某个时间点的版本集合"冻结下来,保证脚本长期可复现。

📘 renv

项目内容
清单build.zig.zon
锁文件不需要——URL + 内容哈希即锁定
常用命令zig fetch --save、zig build
1
2
3
.dependencies = .{
    .zap = .{ .url = "https://.../zap-0.10.0.tar.gz", .hash = "zap-0.10.0-..." },
},
1
zig fetch --save https://github.com/zigzap/zap/archive/refs/tags/v0.10.0.tar.gz

哈希不匹配就拒绝构建,这是 Zig 与众不同的"锁定方式":没有版本范围,只有"这个确切内容"。

📘 Zig · build.zig.zon

项目内容
清单.rockspec
锁文件无
常用命令luarocks install、luarocks make、luarocks list
1
2
3
-- demo-0.1.0-1.rockspec 片段
dependencies = { "lua >= 5.4", "luafilesystem >= 1.8" }
build = { type = "builtin", modules = { demo = "demo.lua" } }
1
2
luarocks make              # 用当前 rockspec 构建并安装
luarocks install --local demo

LuaRocks 没有锁文件;要可复现就写清版本范围,或把依赖连同 rockspec 一起 vendor 进仓库。

📘 LuaRocks · rockspec

项目内容
清单package.json
锁文件package-lock.json、pnpm-lock.yaml、yarn.lock
常用命令npm ci、npm install、pnpm add、pnpm why
1
2
3
4
{
  "dependencies": { "zod": "^3.23.0" },
  "devDependencies": { "typescript": "^7.0.0", "@types/node": "^26.0.0" }
}
1
2
npm ci             # 严格按锁文件安装(CI 专用)
pnpm why zod       # 看某个包为什么被装进来

devDependencies 与 dependencies 要分清:类型包、构建工具放 dev,运行时用到的放 dependencies。

📘 npm · package.json

项目内容
清单package.json
锁文件package-lock.json / pnpm-lock.yaml / yarn.lock
常用命令npm install、npm ci、npm audit、npm outdated
1
2
3
4
5
{
  "engines": { "node": ">=26" },
  "overrides": { "minimist": "^1.2.8" },     // npm 强制指定传递依赖版本
  "scripts": { "start": "node src/main.js" }
}
1
2
npm ls --all | head
npm audit fix

overrides(npm)/resolutions(yarn)/pnpm.overrides 用来强行统一传递依赖版本,是排查"某个老版本漏洞包"的直接手段。

📘 npm · package-lock.json

项目内容
清单composer.json
锁文件composer.lock(必须提交)
常用命令composer install、require、update、show --tree
1
2
3
4
{
  "require": { "php": "^8.5", "guzzlehttp/guzzle": "^7.9" },
  "config": { "platform": { "php": "8.5.0" } }
}
1
2
3
composer install            # 按 composer.lock 安装(CI/生产)
composer update             # 升级并更新锁文件(本地)
composer show --tree guzzlehttp/guzzle

config.platform 可以模拟目标 PHP 版本/扩展,避免"本地 8.5 装得上、线上 8.3 装不上"。

📘 Composer · 依赖

项目内容
清单Gemfile(应用)/ .gemspec(库)
锁文件Gemfile.lock(必须提交)
常用命令bundle install、bundle update、bundle exec、bundle why
1
2
3
4
# Gemfile
source "https://rubygems.org"
gem "rails", "~> 8.0"
gem "puma", ">= 6.4"
1
2
3
bundle install                # 按 Gemfile.lock 安装
bundle update rails            # 只升级 rails
bundle exec ruby main.rb        # 使用锁定版本运行

bundle exec 是 Ruby 项目的纪律:不加前缀就可能用到系统里另一个版本的 gem。

📘 Bundler · Gemfile

包仓库与发布

发布这件事在各语言里的门槛差别很大:有的一个命令就进中心仓库,有的根本没有中心仓库(只能给 Git URL)。

项目内容
注册中心crates.io
发布cargo publish(需 cargo login)
撤回cargo yank --version 1.0.1
私有源在 ~/.cargo/config.toml 里换 registry(如 Artifactory)
1
2
3
cargo login <token>
cargo publish --dry-run     # 先本地检查
cargo publish

publish 前会被 cargo package 的检查拦住明显问题(缺 license、路径依赖等);yank 只是阻止新项目选它,已有 Cargo.lock 不受影响。

📘 cargo publish

项目内容
注册中心无(Swift Package Index 只是目录)
发布打 Git tag,仓库即包
私有私有 Git 仓库 + 凭据
1
2
git tag 1.5.0 && git push origin 1.5.0     # tag 就是"发布"
swift package archive-source                # 打包源码(可选)

SwiftPM 直接按 URL + 版本标签抓取依赖,所以"发布"等于"打语义化版本的 tag";Swift Package Index 负责索引与兼容性检查。

📘 Swift Package Index

项目内容
注册中心无(模块 = Git 仓库)
发布git tag v1.2.3 并推送
代理GOPROXY(proxy.golang.org 或私有代理)
私有GOPRIVATE/GONOSUMDB 绕过校验
1
2
3
git tag v1.2.3 && git push origin v1.2.3
go list -m -versions example.com/demo        # 查看可用版本
GOPROXY=off go build                          # 完全离线(配合 vendor)

主版本 ≥ 2 时,模块路径必须带 /v2(example.com/demo/v2),这是 Go 的强制约定。

📘 Go · 模块版本

项目内容
注册中心PyPI(测试用 TestPyPI)
发布python -m build + twine upload(或 uv publish)
私有源--index-url/PIP_INDEX_URL 指向私有仓库
1
2
3
4
python -m build                    # 生成 wheel 与 sdist
twine check dist/*
twine upload --repository testpypi dist/*
twine upload dist/*

先用 TestPyPI 验证一遍(版本号不能重复);私有包常见做法是自建 devpi/Artifactory 或直接 pip install git+https://...。

📘 PyPI · 发布

项目内容
注册中心Maven Central(通过 Sonatype)
发布maven-publish 插件 + publishToMavenCentral
本地publishToMavenLocal
私有Nexus / Artifactory
1
2
3
4
plugins { `maven-publish` }
publishing {
    publications { create<MavenPublication>("lib") { from(components["java"]) } }
}
1
2
./gradlew publishToMavenLocal
./gradlew publish

发布到 Maven Central 需要签名(GPG)与命名空间验证,一次性配置好之后就是流水线里的一个任务。

📘 Maven Central 发布

项目内容
注册中心Maven Central
发布mvn deploy(配 distributionManagement)
本地mvn install
私有Nexus / Artifactory
1
2
3
<distributionManagement>
  <repository><id>central</id><url>https://...</url></repository>
</distributionManagement>
1
mvn -DskipTests deploy

mvn install 只进本地仓库,deploy 才推送;Central 要求提供 sources/javadoc 附件并签名。

📘 Maven · 部署

项目内容
注册中心无官方;vcpkg registry、Conan Center
发布vcpkg 提交 port / conan upload
私有自建 Conan server、私有 vcpkg registry
1
2
3
conan create . --build=missing
conan upload demo/0.1.0 -r my-remote
vcpkg x-add-version demo            # 向 vcpkg 提交新版本

C++ 的现实是:分发源码与构建脚本比分发二进制更常见(ABI 不统一);二进制分发通常绑定具体编译器/标准库版本。

📘 Conan · 上传、vcpkg registry

项目内容
注册中心无
发布打源码 tarball + make install;或交给发行版打包
私有内部源码仓库 / 内部 apt 源
1
2
./configure --prefix=/usr/local && make && sudo make install
make dist                      # Autotools 的源码包目标

分发约定是 ./configure && make && make install 或 CMake 的 cmake --install;pkg-config 文件随包安装,供其他项目发现。

📘 GNU 编码标准 · 发布

项目内容
注册中心General Registry(GitHub PR 提交)
发布打 tag + 向 Registry 提 PR(可用 Pkg.Registry/LocalRegistry)
私有自建 registry(Git 仓库)+ Pkg.Registry.add
1
2
3
4
using Pkg
Pkg.activate(".")
Pkg.Registry.add("General")
# 发布:给包仓库打 tag 后向 General registry 提 PR

Julia 的注册中心本质是一个 Git 仓库(记录包名、UUID、版本与仓库地址),所以私有 registry 就是"自己的一个 Git 仓库"。

📘 Registries

项目内容
注册中心NuGet.org
发布dotnet pack + dotnet nuget push
私有nuget.config 配置私有 feed
1
2
dotnet pack -c Release -o out
dotnet nuget push out/Demo.1.0.0.nupkg --api-key $KEY --source https://api.nuget.org/v3/index.json

内部包常见于 Azure Artifacts / GitHub Packages / 自建 BaGet;nuget.config 决定还原时按什么顺序查源。

📘 dotnet pack

项目内容
注册中心pub.dev
发布dart pub publish
私有Git 依赖、dependency_overrides、自建 pub server
1
2
dart pub publish --dry-run     # 先检查要发布什么
dart pub publish

发布前 pub.dev 会做静态检查(分数);私有包通常直接用 git: 依赖或 path: 依赖,团队内共享即可。

📘 dart pub publish

项目内容
注册中心CRAN(人工审核)、Bioconductor、R-universe
发布devtools::release() / R CMD check --as-cran + 提交
私有内部包仓库(miniCRAN、drat)
1
2
devtools::check_win_devel()      # 提交前的多平台检查
devtools::release()               # 引导式发布流程

CRAN 审核严格(示例、文档、跨平台检查都要过),所以很多团队先用 R-universe 或内部仓库分发,成熟后再上 CRAN。

📘 CRAN · 提交

项目内容
注册中心无
发布打 tarball / 推 Git tag,消费方用 URL + 哈希
私有任意 HTTP 服务或私有 Git
1
zig fetch --save https://example.com/demo-0.1.0.tar.gz

没有版本解析,所以"发布"就是提供不可变内容(tarball 或 tag);哈希由消费方第一次 fetch 时记录并提交进 build.zig.zon。

📘 Zig · 依赖

项目内容
注册中心luarocks.org
发布luarocks upload
私有自建 rocks server 或只走源码
1
2
luarocks upload demo-0.1.0-1.rockspec --api-key $KEY
luarocks install demo

发布前先用 luarocks make 在本地走一遍完整构建,确认 dependencies 与 build.modules 正确。

📘 LuaRocks · 上传

项目内容
注册中心npm Registry
发布npm publish(scope 包需 --access public)
私有私有 scope + 私有 registry(Verdaccio、GitHub Packages)
1
2
3
4
npm login
npm publish --dry-run           # 检查会发布哪些文件
npm publish --access public
npm version patch && npm publish  # 自动改版本并发布

files 字段与 .npmignore 决定包里带什么;TypeScript 包要同时发 .js 与 .d.ts(declaration: true)。

📘 npm · 发布

项目内容
注册中心npm Registry
发布npm publish(prepublishOnly 里跑测试/构建)
私有scoped 包 + 私有 registry
1
2
3
4
{
  "files": ["dist"],
  "scripts": { "prepublishOnly": "npm test && npm run build" }
}
1
2
npm pack            # 生成 tarball 并检查内容(发布前强烈建议)
npm publish

npm pack 是最便宜的预检:它把将要上传的 tarball 解出来给你看,避免把源码、密钥或测试数据打进包里。

📘 npm · 包内容

项目内容
注册中心Packagist
发布提交 VCS 地址即可(打 tag 触发更新)
私有Private Packagist、Satis、Composer 的 repositories
1
2
3
composer validate --strict
# 在 Packagist 提交 https://github.com/you/demo 后,推 tag 即发布版本
git tag v1.0.0 && git push origin v1.0.0

Packagist 不存代码,只做索引:composer.json 里的 name + VCS 仓库 + tag 就是一次发布;私有包用 repositories 指向内部 VCS 或 Satis。

📘 Packagist

项目内容
注册中心RubyGems.org
发布gem build + gem push(rake release 自动化)
私有私有 gem server(Gem in a Box、Gemfury)
1
2
3
gem build demo.gemspec
gem push demo-0.1.0.gem
rake release            # 打 tag + 构建 + 推送(项目里常用)

gem push 前确认 spec.files 用 git ls-files 之类的方式限定,避免把本地临时文件带进包里。

📘 RubyGems · 发布

版本约束、工作区与常见坑

最后看三件事:版本号怎么写、多包项目怎么组织(workspace)、最常踩的坑。

项目内容
版本语法"1.2.3" = ^1.2.3(≥1.2.3 且 <2.0.0)、~1.2.3、=1.2.3、*
工作区[workspace] 共享 Cargo.lock 与 target/
常见坑同一 crate 出现多个大版本(cargo tree -d)、feature 统一(unification)导致意外启用
1
2
3
4
5
6
[workspace]
members = ["crates/*"]
resolver = "3"

[workspace.dependencies]
serde = "1"

resolver = "2"/"3" 决定 feature 与目标平台的合并规则;子 crate 用 serde.workspace = true 复用统一版本。

📘 Cargo · 工作区

项目内容
版本语法from: "1.5.0"(语义化范围)、.exact("1.5.0")、.branch、.revision
工作区一个 Package.swift 可有多个 target/product;多包用本地路径依赖
常见坑同一依赖被要求不同版本 → 解析失败;Package.resolved 冲突
1
2
.package(path: "../LocalLib"),
.package(url: "https://.../swift-arg.git", exact: "1.5.0"),

SwiftPM 对每个依赖只能选一个版本(不像 Cargo 允许多版本共存),所以版本冲突必须显式解决。

📘 SwiftPM · 依赖规则

项目内容
版本语法语义化标签 v1.2.3;伪版本 v0.0.0-20260919120000-abcdef123456
工作区go.work(多模块本地开发)
常见坑主版本 ≥2 必须在模块路径写 /v2;replace 只在本机生效
1
2
go work init ./a ./b
go work use ./c
// go.mod
require example.com/demo/v2 v2.1.0     // 主版本必须体现在路径里
replace example.com/demo => ../demo    // 本地调试用

Go 的 MVS 只会升不会自动降;要降版本得显式 go get pkg@older。

📘 Go · 模块版本

项目内容
版本语法~=1.4.2(兼容版本)、>=1.2,<2、==1.4.2、*
工作区单仓库多包常用 src/ 布局 + path 依赖;uv 支持 workspace
常见坑无锁文件不可复现、依赖冲突(ResolutionImpossible)、extras 写法遗漏
1
2
3
4
dependencies = ["httpx>=0.27,<1"]

[project.optional-dependencies]
dev = ["pytest>=8"]
1
2
uv sync --all-extras
pip install "httpx>=0.27,<1"

pip 的解析器从 20.3 起会回溯求解,但冲突时仍可能报 ResolutionImpossible;用 uv 这类带锁的工具能提前发现问题。

📘 PEP 440 · 版本标识

项目内容
版本语法Gradle 的 1.2.3 表示"最高 1.2.3";可用 strictly/prefer/require 精确控制
工作区Gradle 多模块(settings.gradle.kts 里 include)
常见坑传递依赖版本冲突默认选最高、Kotlin 插件版本与 Gradle 版本匹配
1
2
3
dependencies {
    implementation("org.slf4j:slf4j-api") { version { strictly("2.0.16") } }
}

多模块项目用 implementation(project(":core")) 引用本地模块;版本集中在 libs.versions.toml 管理最省心。

📘 Gradle · 版本约束

项目内容
版本语法Maven 范围 [1.0,2.0)、[1.0,);普通写法表示"推荐版本"
工作区Maven 多模块(父 pom + <modules>)
常见坑最近优先(nearest-wins)导致传递依赖版本漂移、依赖冲突只在运行期暴露
1
2
3
4
5
<dependency>
  <groupId>com.google.guava</groupId>
  <artifactId>guava</artifactId>
  <version>[33.0,34.0)</version>
</dependency>

用 dependencyManagement 把关键传递依赖钉在顶层,是防漂移的标准手段;mvn dependency:tree -Dverbose 能看出被剪掉的版本。

📘 Maven · 依赖调解

项目内容
版本语法vcpkg 用 overrides 固定版本;Conan 用 requires + 语义化版本
工作区CMake 的 add_subdirectory 或 Conan workspace
常见坑ABI 不一致(编译器/标准库不同)、ODR 违规、同名符号冲突
1
{ "overrides": [{ "name": "fmt", "version": "11.0.2" }] }

C++ 的"依赖地狱"根源是二进制不兼容:即使版本号相同,编译器或 -D_GLIBCXX_USE_CXX11_ABI 不同也可能链不上。

📘 vcpkg · 版本控制

项目内容
版本语法无(依赖系统包管理器)
工作区无(Make/CMake 子目录)
常见坑头文件与库版本不匹配、符号冲突、pkg-config 找不到时静默用错版本
1
2
pkg-config --modversion openssl
pkg-config --exists "openssl >= 3.0" && echo ok

在 configure/CMake 里显式检查版本(而不只是"找到了库")能避免运行期才暴露的 ABI 问题。

📘 pkg-config

项目内容
版本语法[compat] 写 "1.7"(= 1.7 ≤ x < 2)、"~1.7.2"、"=1.7.2"
工作区多个项目共用 Manifest.toml 或用 Pkg.develop 挂本地包
常见坑Manifest 与 Project 不一致、忘记 Pkg.instantiate、registry 未更新
1
2
3
[compat]
DataFrames = "1.7"
julia = "1.13"

[compat] 是 Julia 包发布的必需项:没有它,下游无法保证兼容范围。

📘 Pkg · 兼容性

项目内容
版本语法NuGet 范围 [1.0,2.0)、1.2.*、[1.0]
工作区解决方案(.sln)+ 中央包管理
常见坑传递依赖降级(NU1605 警告,可提升为错误)、多目标框架差异
1
<PackageReference Include="Serilog" Version="[4.2.0,5.0.0)" />
1
dotnet restore -warnaserror:NU1605     # 把降级警告当错误

中央包管理(Directory.Packages.props)让版本只写一次;多目标框架用 Condition 区分。

📘 NuGet · 版本范围

项目内容
版本语法^1.2.0(默认,<2.0.0)、>=1.2.0 <2.0.0、any
工作区pub workspace(Dart 3.6+)
常见坑版本求解失败(多个包要求互斥区间)、any 引入不兼容更新
1
2
dependency_overrides:      # 应急:强制覆盖某个包的依赖
  http: ^1.2.0

dart pub outdated 会列出"可升级、可大版本升级、被约束卡住"三类,是排查求解失败的第一步。

📘 pub · 版本约束

项目内容
版本语法DESCRIPTION 里写 pkg (>= 1.2.0),没有范围语法
工作区renv 项目级环境(一个项目一份锁文件)
常见坑CRAN 包不保证向后兼容、系统库依赖(libcurl/openssl)缺失、Bioconductor 版本与 R 版本绑定
1
2
# DESCRIPTION
Imports: dplyr (>= 1.1.0), tidyr
1
renv::diagnostics()      # 检查环境与锁文件是否一致

Bioconductor 的包按 R 版本发布(BiocManager 会选匹配版本),跨版本升级要整体走一次 release 流程。

📘 renv · 版本

项目内容
版本语法无范围:URL + 哈希(哈希即精确版本)
工作区无(build.zig 里组合模块)
常见坑依赖 API 不稳定(0.x 时代常破坏性变更)、重新打包 tarball 会改变哈希
1
2
zig fetch --save=dep https://example.com/dep.tar.gz   # 记录哈希
zig build --summary all                                # 查看依赖与耗时

因为哈希对内容敏感,依赖方不要"重新打包"上游 tarball;直接引用官方发布物或 Git archive。

📘 Zig · 包管理

项目内容
版本语法rockspec 里 dependencies = { "lua >= 5.4", "lfs ~> 1.8" }
工作区无(LUA_PATH/LUA_CPATH 控制搜索路径)
常见坑全局安装互相污染、C 模块与 Lua 版本/ABI 绑定、路径写错导致 require 失败
1
2
print(package.path)     -- 查看模块搜索路径
print(package.cpath)     -- C 模块搜索路径

团队项目建议 luarocks install --tree ./lua_modules --local 把依赖装进项目目录,再用 LUA_PATH 指向它。

📘 LuaRocks · 依赖

项目内容
版本语法^1.2.3(默认)、~1.2.3、>=1.2 <2、workspace:*
工作区npm/pnpm/yarn workspaces、TS project references
常见坑peer 依赖缺失、锁文件不同步导致 npm ci 失败、幽灵依赖(pnpm 收紧)
1
{ "workspaces": ["packages/*"] }
1
2
npm ci                          # 锁文件与 package.json 不一致会直接报错
pnpm -r exec tsc --noEmit        # 在所有工作区跑类型检查

类型包与运行时包版本要对齐(@types/node 与 Node 版本),否则会出现在编译期能过、运行期属性不存在的怪问题。

📘 npm workspaces

项目内容
版本语法同 npm(^/~/>=),overrides 强制传递依赖版本
工作区workspaces + 单一锁文件
常见坑多个版本的同名包被打进 bundle(体积翻倍)、CJS/ESM 双包问题(dual package hazard)
1
2
npm ls lodash            # 看是否装了多个版本
npm dedupe                # 尽量合并版本

打包前用 npm ls <pkg> 确认没有重复版本;库作者要同时提供 ESM 与 CJS 时,注意"同一模块被加载两次"的状态问题。

📘 npm · overrides

项目内容
版本语法^1.2、~1.2、>=1.2 <2.0、dev-main
工作区无(多包用 path repository)
常见坑平台扩展要求(ext-json、ext-mbstring)、composer.lock 与生产不一致、dev 分支被误用
1
2
3
4
{
  "require": { "php": "^8.5", "ext-json": "*" },
  "repositories": [{ "type": "path", "url": "../local-lib" }]
}

composer check-platform-reqs 能在部署前验证目标环境是否满足扩展与 PHP 版本要求。

📘 Composer · 版本约束

项目内容
版本语法~> 1.2(≥1.2 且 <2.0)、~> 1.2.3(≥1.2.3 且 <1.3)、>= 1.2
工作区无原生 workspace;多 gem 用 path 依赖
常见坑原生扩展编译失败、Gemfile.lock 平台差异、Bundler 版本不一致
1
2
gem "rails", "~> 8.0"
gem "local_lib", path: "../local_lib"
1
2
bundle lock --add-platform x86_64-linux    # 让锁文件兼容部署平台
bundle platform                             # 查看当前平台

~> 的宽度取决于位数:~> 1.2 允许到 2.0 之前,~> 1.2.3 只到 1.3 之前——这是 Ruby 里最容易写错的版本约束。

📘 Bundler · Gemfile


本页覆盖 18 种语言的包管理与模块:模块与命名空间、依赖声明与锁定、包仓库与发布、版本约束与工作区。清单/锁文件名与命令均按各语言当前稳定版整理;“没有"这种机制的语言(C 的模块、Go/Zig 的中心仓库)也在正文中说明了替代做法。

版本基线(2026-09):Rust 1.98、Swift 6.4、Go 1.27、Python 3.14、Kotlin 2.4、Java 26(LTS 25)、C++23、C23、Julia 1.13、C# 14 / .NET 10、Dart 3.13、R 4.6、Zig 0.15、Lua 5.5、TypeScript 7、Node 26、PHP 8.5、Ruby 4.0。

最后修改 September 19, 2026: 更新 (934259854)