Skip to content

Commit

Permalink
Merge pull request #188 from polac24/bartosz/20230310-lipo
Browse files Browse the repository at this point in the history
Introduce xclipo to mock lipo
  • Loading branch information
polac24 authored Mar 21, 2023
2 parents 2f10c6a + de24e60 commit 11eabda
Show file tree
Hide file tree
Showing 21 changed files with 478 additions and 65 deletions.
4 changes: 2 additions & 2 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -51,8 +51,8 @@
"repositoryURL": "https://github.com/tuist/XcodeProj.git",
"state": {
"branch": null,
"revision": "c75c3acc25460195cfd203a04dde165395bf00e0",
"version": "8.7.1"
"revision": "fae27b48bc14ff3fd9b02902e48c4665ce5a0793",
"version": "8.9.0"
}
},
{
Expand Down
17 changes: 15 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -16,7 +16,7 @@ let package = Package(
.package(url: "https://github.com/marmelroy/Zip.git", from: "2.1.2"),
.package(url: "https://github.com/jpsim/Yams.git", from: "5.0.0"),
.package(url: "https://github.com/apple/swift-argument-parser", from: "0.0.1"),
.package(url: "https://github.com/tuist/XcodeProj.git", from: "8.7.1"),
.package(url: "https://github.com/tuist/XcodeProj.git", from: "8.9.0"),
.package(url: "https://github.com/apple/swift-docc-plugin", from: "1.0.0"),
],
targets: [
Expand All @@ -40,6 +40,10 @@ let package = Package(
name: "xclibtool",
dependencies: ["XCRemoteCache", "xclibtoolSupport"]
),
.target(
name: "xclipo",
dependencies: ["XCRemoteCache"]
),
.target(
name: "xcpostbuild",
dependencies: ["XCRemoteCache"]
Expand All @@ -62,7 +66,16 @@ let package = Package(
.target(
// Wrapper target that builds all binaries but does nothing in runtime
name: "Aggregator",
dependencies: ["xcprebuild", "xcswiftc", "xclibtool", "xcpostbuild", "xcprepare", "xcld", "xcldplusplus"]
dependencies: [
"xcprebuild",
"xcswiftc",
"xclibtool",
"xcpostbuild",
"xcprepare",
"xcld",
"xcldplusplus",
"xclipo",
]
),
.testTarget(
name: "XCRemoteCacheTests",
Expand Down
3 changes: 2 additions & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -195,6 +195,7 @@ Configure Xcode targets that **should use** XCRemoteCache:
* `CC` - `xccc_file` from your `.rcinfo` configuration (e.g. `xcremotecache/xccc`)
* `SWIFT_EXEC` - location of `xcprepare` (e.g. `xcremotecache/xcswiftc`)
* `LIBTOOL` - location of `xclibtool` (e.g. `xcremotecache/xclibtool`)
* `LIPO` - location of `xclipo` (e.g. `xcremotecache/xclipo`)
* `LD` - location of `xcld` (e.g. `xcremotecache/xcld`)
* `LDPLUSPLUS` - location of `xcldplusplus` (e.g. `xcremotecache/xcldplusplus`)
* `XCRC_PLATFORM_PREFERRED_ARCH` - `$(LINK_FILE_LIST_$(CURRENT_VARIANT)_$(PLATFORM_PREFERRED_ARCH):dir:standardizepath:file:default=arm64)`
Expand Down Expand Up @@ -267,7 +268,7 @@ $ xcremotecache/xcprepare mark --configuration Debug --platform iphonesimulator

That command creates an empty file on a remote server which informs that for given sha, configuration, platform, Xcode versions etc. all artifacts are available.

_Note that for the `producer` mode, the prebuild build phase and `xccc`, `xcld`, `xcldplusplus`, `xclibtool` wrappers become no-op, so it is recommended to not add them for the `producer` mode._
_Note that for the `producer` mode, the prebuild build phase and `xccc`, `xcld`, `xcldplusplus`, `xclibtool`, `xclipo` wrappers become no-op, so it is recommended to not add them for the `producer` mode._

##### 7. Generalize `-Swift.h` (Optional only if using static library with a bridging header with public `NS_ENUM` exposed from ObjC)

Expand Down
2 changes: 1 addition & 1 deletion Rakefile
Original file line number Diff line number Diff line change
Expand Up @@ -10,7 +10,7 @@ DERIVED_DATA_DIR = File.join('.build').freeze
RELEASES_ROOT_DIR = File.join('releases').freeze

EXECUTABLE_NAME = 'XCRemoteCache'
EXECUTABLE_NAMES = ['xclibtool', 'xcpostbuild', 'xcprebuild', 'xcprepare', 'xcswiftc', 'xcld', 'xcldplusplus']
EXECUTABLE_NAMES = ['xclibtool', 'xcpostbuild', 'xcprebuild', 'xcprepare', 'xcswiftc', 'xcld', 'xcldplusplus', 'xclipo']
PROJECT_NAME = 'XCRemoteCache'

SWIFTLINT_ENABLED = true
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -19,31 +19,42 @@

import Foundation

enum XCLibtoolCreateUniversalBinaryError: Error {
enum XCCreateUniversalBinaryError: Error {
/// Missing ar libraries that should constitute an universal build
case missingInputLibrary
}

/// Wrapper for `libtool` call for creating an universal binary
class XCLibtoolCreateUniversalBinary: XCLibtoolLogic {
/// Wrapper for `libtool`/`lipo` call for creating an universal binary
class XCCreateUniversalBinary: XCLibtoolLogic {
private let output: URL
private let tempDir: URL
private let firstInputURL: URL
private let toolName: String
private let fallbackCommand: String

init(output: String, inputs: [String]) throws {
init(
output: String,
inputs: [String],
toolName: String,
fallbackCommand: String
) throws {
self.output = URL(fileURLWithPath: output)
guard let firstInput = inputs.first else {
throw XCLibtoolCreateUniversalBinaryError.missingInputLibrary
throw XCCreateUniversalBinaryError.missingInputLibrary
}
let firstInputURL = URL(fileURLWithPath: firstInput)
// inputs are place in $TARGET_TEMP_DIR/Objects-normal/$ARCH/Binary/$TARGET_NAME.a
// TODO: find better (stable) technique to determine `$TARGET_TEMP_DIR`
errorLog("\(firstInputURL.absoluteString)")

tempDir = firstInputURL
.deletingLastPathComponent()
.deletingLastPathComponent()
.deletingLastPathComponent()
.deletingLastPathComponent()
self.firstInputURL = firstInputURL
self.toolName = toolName
self.fallbackCommand = fallbackCommand
}

func run() {
Expand All @@ -55,7 +66,7 @@ class XCLibtoolCreateUniversalBinary: XCLibtoolLogic {
config = try XCRemoteCacheConfigReader(srcRootPath: srcRoot.path, fileReader: fileManager)
.readConfiguration()
} catch {
errorLog("Libtool initialization failed with error: \(error). Fallbacking to libtool")
errorLog("\(toolName) initialization failed with error: \(error). Fallbacking to \(fallbackCommand)")
fallbackToDefault()
}

Expand All @@ -74,22 +85,21 @@ class XCLibtoolCreateUniversalBinary: XCLibtoolLogic {
// that these are already an universal binary
try fileManager.spt_forceLinkItem(at: firstInputURL, to: output)
} catch {
errorLog("Libtool failed with error: \(error). Fallbacking to libtool")
errorLog("\(toolName) failed with error: \(error). Fallbacking to \(fallbackCommand)")
do {
try fileManager.removeItem(at: markerURL)
fallbackToDefault()
} catch {
exit(1, "FATAL: libtool failed with error: \(error)")
exit(1, "FATAL: \(fallbackCommand) failed with error: \(error)")
}
}
}

private func fallbackToDefault() -> Never {
let args = ProcessInfo().arguments
let command = "libtool"
let paramList = [command] + args.dropFirst()
let paramList = [fallbackCommand] + args.dropFirst()
let cargs = paramList.map { strdup($0) } + [nil]
execvp(command, cargs)
execvp(fallbackCommand, cargs)

/// C-function `execv` returns only when the command fails
exit(1)
Expand Down
7 changes: 6 additions & 1 deletion Sources/XCRemoteCache/Commands/Libtool/XCLibtool.swift
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,12 @@ public class XCLibtool {
stepDescription: "Libtool"
)
case .createUniversalBinary(let output, let inputs):
logic = try XCLibtoolCreateUniversalBinary(output: output, inputs: inputs)
logic = try XCCreateUniversalBinary(
output: output,
inputs: inputs,
toolName: "Libtool",
fallbackCommand: "libtool"
)
}
}

Expand Down
50 changes: 50 additions & 0 deletions Sources/XCRemoteCache/Commands/Libtool/XCLipo.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,50 @@
// Copyright (c) 2023 Spotify AB.
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

import Foundation

/// Wrapper for a `lipo` tool that creates a fat archive
public class XCLipo {
private let logic: XCLibtoolLogic

public init(
output: String,
inputs: [String],
fallbackCommand: String,
stepDescription: String
) throws {
errorLog("\(output)")
errorLog("\(inputs.joined(separator: ","))")
logic = try XCCreateUniversalBinary(
output: output,
inputs: inputs,
toolName: stepDescription,
fallbackCommand: fallbackCommand
)
}

/// Handles a `-create` action which is responsible to create a fat archive
/// If remote cache can reuse artifacts from a remote cache, it just links any of input
/// files to the destination (output) location because the binary in XCRC artifact already
/// contains a fat library
/// If a remote artifact cannot be reused, a fallback to the `lipo` command is performed
public func run() {
logic.run()
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -50,6 +50,7 @@ class XcodeProjBuildSettingsIntegrateAppender: BuildSettingsIntegrateAppender {
result["CC"] = wrappers.cc.path
result["LD"] = wrappers.ld.path
result["LIBTOOL"] = wrappers.libtool.path
result["LIPO"] = wrappers.lipo.path
result["LDPLUSPLUS"] = wrappers.ldplusplus.path
}

Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -52,6 +52,7 @@ extension IntegrateContext {
cc: binariesDir.appendingPathComponent("xccc"),
swiftc: binariesDir.appendingPathComponent("xcswiftc"),
libtool: binariesDir.appendingPathComponent("xclibtool"),
lipo: binariesDir.appendingPathComponent("xclipo"),
ld: binariesDir.appendingPathComponent("xcld"),
ldplusplus: binariesDir.appendingPathComponent("xcldplusplus"),
prebuild: binariesDir.appendingPathComponent("xcprebuild"),
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,7 @@ struct XCRCBinariesPaths {
let cc: URL
let swiftc: URL
let libtool: URL
let lipo: URL
let ld: URL
let ldplusplus: URL
let prebuild: URL
Expand Down
71 changes: 71 additions & 0 deletions Sources/xclipo/XCLipoMain.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,71 @@
// Copyright (c) 2023 Spotify AB.
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

import Foundation
import XCRemoteCache

/// Wrapper for a `lipo` program that links any of input binaries to the destination paths
/// Fallbacks to a standard `lipo` when the Ramote cache is not applicable (e.g. modified sources)
public class XCLipoMain {
public init() { }

public func main() {
let args = ProcessInfo().arguments
var output: String?
var create = false
var inputs: [String] = []

var i = 1
while i < args.count {
switch args[i] {
case "-output":
output = args[i + 1]
i += 1
case "-create":
create = true
default:
inputs.append(args[i])
}
i += 1
}
let lipoCommand = "lipo"
guard let output = output, !inputs.isEmpty, create else {
print("Fallbacking to compilation using \(lipoCommand).")

let args = ProcessInfo().arguments
let paramList = [lipoCommand] + args.dropFirst()
let cargs = paramList.map { strdup($0) } + [nil]
execvp(lipoCommand, cargs)

/// C-function `execv` returns only when the command fails
exit(1)
}

do {
try XCLipo(
output: output,
inputs: inputs,
fallbackCommand: lipoCommand,
stepDescription: "xclipo"
).run()
} catch {
exit(1, "Failed with: \(error). Args: \(args)")
}
}
}
22 changes: 22 additions & 0 deletions Sources/xclipo/main.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,22 @@
// Copyright (c) 2023 Spotify AB.
//
// Licensed to the Apache Software Foundation (ASF) under one
// or more contributor license agreements. See the NOTICE file
// distributed with this work for additional information
// regarding copyright ownership. The ASF licenses this file
// to you under the Apache License, Version 2.0 (the
// "License"); you may not use this file except in compliance
// with the License. You may obtain a copy of the License at
//
// http://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing,
// software distributed under the License is distributed on an
// "AS IS" BASIS, WITHOUT WARRANTIES OR CONDITIONS OF ANY
// KIND, either express or implied. See the License for the
// specific language governing permissions and limitations
// under the License.

import XCRemoteCache

XCLipoMain().main()
Original file line number Diff line number Diff line change
Expand Up @@ -34,6 +34,7 @@ class XcodeProjBuildSettingsIntegrateAppenderTests: XCTestCase {
cc: binariesDir.appendingPathComponent("xccc"),
swiftc: binariesDir.appendingPathComponent("xcswiftc"),
libtool: binariesDir.appendingPathComponent("xclibtool"),
lipo: binariesDir.appendingPathComponent("lipo"),
ld: binariesDir.appendingPathComponent("xcld"),
ldplusplus: binariesDir.appendingPathComponent("xcldplusplus"),
prebuild: binariesDir.appendingPathComponent("xcprebuild"),
Expand Down
Loading

0 comments on commit 11eabda

Please sign in to comment.