me icon indicating copy to clipboard operation
me copied to clipboard

学习 MacOS 开发 (Part 9: Swift 编译环境)

Open nonocast opened this issue 2 years ago • 0 comments

Packaging

swift的hello world都是从hello.swift开始,

print("hello world")

无需编译直接运行:

~ swift hello.swift
hello world

那我们现在再创建一个foo.swift

func hello() {
  print("hello world")
}

同时,将hello.swift改为调用,

hello()

这时候问题就来了,

  • nodejs通过require './foo'就可以引用其他文件,一个带package.json的目录自动就可以是一个module,需要社区复用时可以借助npm
  • python通过import相对目录和绝对目录直接可以导入,一个目录直接成模块
  • c/c++是通过每个c/c++自成object file然后link组团,同Java, C#

常规来说,c/c++是编译型语言,javascript/python是解释型语言,那么swift是什么呢?

  • 我理解swift同时实现了这两个类型,通过swift命令实现了解释功能,通过swiftc实现了编译功能
  • swift/swiftc和java/javac是有区别的,javac将java程序编译成java bytecode,即JIT可以理解的中间语言,java则是通过JIT运行java bytecode,而swift是直接解释script,而swiftc则是编译成机器码, 后续运行不需要中间过程
  • 所以需要同时需要2套思维去看swift, 事实上,我们用的更多的是编译这套体系,解释只是cookie而已,所以应该用c/c++/oc的思维去理解swift会更合适

在官网的Swift.org - Getting Started中介绍了Package Manager,可以通过swift package init, swift build, swift run来实现打包, 我理解内部是通过swiftc来实现的。

  • 所以swift hello.swift更类似swift < hello.swift,只是一个字符串的输入,如果你需要解释来运行多个文件,网上也有人说先merge所有swift然后交给swift

swiftc

hello.swift

print("hello world")

swiftc hello.swift后会生成hello可执行文件,这个一点问题没有。

我们来看foo.swift和main.swift两个文件,

foo.swift

func foo() { print("I'm foo") }

main.swift

foo()

编译和运行:

➜  swiftc -c main.swift foo.swift -module-name hello
➜  swiftc -emit-executable main.o foo.o -o app 
➜  ./app
i'm foo
  • 你会发现原来main和foo需要先编译出object file,这就走回了c/oc的老路
  • 然后链接形成app
  • 当编译多个文件时,有且仅有一个main.swift,只有这个文件可以在方法外直接写代码,直接写的代码等同于entry point
  • 当用其他文件时需要辅以"@main"的标记
  • 可以使用通配符 swiftc -c *.swift -module-name hello, swiftc -emit-executable *.o -o app来完成这个过程

对应的Makefile:

app: obj
	swiftc -emit-executable main.o foo.o -o app

obj: main.swift foo.swift
	swiftc -c main.swift foo.swift -module-name hello

run:
	./app

通过swiftc --help你可以看到如下指令:

  • -emit-assembly: Emit assembly file(s) (-S)
  • -emit-executable: Emit a linked executable
  • -emit-library: Emit a linked library
  • -emit-object: Emit object file(s) (-c)

此外, 可以通过file, nm, gobjdump来观察一下编译出来的object file和可执行文件,这里就省略了。

swift build

当你理解了swiftc后再去理解swift build就会有不一样的体感。

对应刚才结构的Package.swift

import PackageDescription

let package = Package(
    name: "foo",
    targets: [
      .executableTarget(
        name: "foo",
        path: ".",
        exclude: ["Makefile", "README.md"],
        sources: ["main.swift", "foo.swift"]
      )
    ]
)

默认的path是Sources,这个文档中都有介绍:

/// Creates an executable target.
///
/// An executable target can contain either Swift or C-family source files, but not both. It contains code that
/// is built as an executable module that can be used as the main target of an executable product. The target
/// is expected to either have a source file named `main.swift`, `main.m`, `main.c`, or `main.cpp`, or a source
/// file that contains the `@main` keyword.
///
/// - Parameters:
///   - name: The name of the target.
///   - dependencies: The dependencies of the target. A dependency can be another target in the package or a product from a package dependency.
///   - path: The custom path for the target. By default, the Swift Package Manager requires a target's sources to reside at predefined search paths;
///       for example, `[PackageRoot]/Sources/[TargetName]`.
///       Don't escape the package root; for example, values like `../Foo` or `/Foo` are invalid.
///   - exclude: A list of paths to files or directories that the Swift Package Manager shouldn't consider to be source or resource files.
///       A path is relative to the target's directory.
///       This parameter has precedence over the `sources` parameter.
///   - sources: An explicit list of source files. If you provide a path to a directory,
///       the Swift Package Manager searches for valid source files recursively.
///   - resources: An explicit list of resources files.
///   - publicHeadersPath: The directory containing public headers of a C-family library target.
///   - cSettings: The C settings for this target.
///   - cxxSettings: The C++ settings for this target.
///   - swiftSettings: The Swift settings for this target.
///   - linkerSettings: The linker settings for this target.
static func executableTarget(
    name: String,
    dependencies: [Target.Dependency] = [],
    path: String? = nil,
    exclude: [String] = [],
    sources: [String]? = nil,
    resources: [Resource]? = nil,
    publicHeadersPath: String? = nil,
    cSettings: [CSetting]? = nil,
    cxxSettings: [CXXSetting]? = nil,
    swiftSettings: [SwiftSetting]? = nil,
    linkerSettings: [LinkerSetting]? = nil
) -> Target

library

swiftc生成.a swiftc -emit-module -emit-library -static bar.swift

swiftc生成.dylib swiftc -emit-module -emit-library bar.swift

swift build在swiftc基础上解决自动依赖处理的逻辑,可以直接引用github的repo

关于library后续再展开。


更新1:

  • swiftc -o app main.swift content.swift 可以一步完成编译和链接。

参考阅读

nonocast avatar May 01 '22 03:05 nonocast