diff --git a/Package.resolved b/Package.resolved index 2205fb44..f2b131ac 100644 --- a/Package.resolved +++ b/Package.resolved @@ -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" } }, { diff --git a/Package.swift b/Package.swift index ef5b54b8..426a40d9 100644 --- a/Package.swift +++ b/Package.swift @@ -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: [ @@ -40,6 +40,10 @@ let package = Package( name: "xclibtool", dependencies: ["XCRemoteCache", "xclibtoolSupport"] ), + .target( + name: "xclipo", + dependencies: ["XCRemoteCache"] + ), .target( name: "xcpostbuild", dependencies: ["XCRemoteCache"] @@ -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", diff --git a/README.md b/README.md index 3eb816fe..0f4ece48 100755 --- a/README.md +++ b/README.md @@ -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)` @@ -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) diff --git a/Rakefile b/Rakefile index 808fa6d7..3934c77f 100644 --- a/Rakefile +++ b/Rakefile @@ -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 diff --git a/Sources/XCRemoteCache/Commands/Libtool/XCLibtoolCreateUniversalBinary.swift b/Sources/XCRemoteCache/Commands/Libtool/XCCreateUniversalBinary.swift similarity index 78% rename from Sources/XCRemoteCache/Commands/Libtool/XCLibtoolCreateUniversalBinary.swift rename to Sources/XCRemoteCache/Commands/Libtool/XCCreateUniversalBinary.swift index d9dcecce..b05117d8 100644 --- a/Sources/XCRemoteCache/Commands/Libtool/XCLibtoolCreateUniversalBinary.swift +++ b/Sources/XCRemoteCache/Commands/Libtool/XCCreateUniversalBinary.swift @@ -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() { @@ -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() } @@ -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) diff --git a/Sources/XCRemoteCache/Commands/Libtool/XCLibtool.swift b/Sources/XCRemoteCache/Commands/Libtool/XCLibtool.swift index 70c5004a..306818ca 100644 --- a/Sources/XCRemoteCache/Commands/Libtool/XCLibtool.swift +++ b/Sources/XCRemoteCache/Commands/Libtool/XCLibtool.swift @@ -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" + ) } } diff --git a/Sources/XCRemoteCache/Commands/Libtool/XCLipo.swift b/Sources/XCRemoteCache/Commands/Libtool/XCLipo.swift new file mode 100644 index 00000000..28669ee7 --- /dev/null +++ b/Sources/XCRemoteCache/Commands/Libtool/XCLipo.swift @@ -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() + } +} diff --git a/Sources/XCRemoteCache/Commands/Prepare/Integrate/BuildSettingsIntegrateAppender.swift b/Sources/XCRemoteCache/Commands/Prepare/Integrate/BuildSettingsIntegrateAppender.swift index ccccfc02..3925ca1a 100644 --- a/Sources/XCRemoteCache/Commands/Prepare/Integrate/BuildSettingsIntegrateAppender.swift +++ b/Sources/XCRemoteCache/Commands/Prepare/Integrate/BuildSettingsIntegrateAppender.swift @@ -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 } diff --git a/Sources/XCRemoteCache/Commands/Prepare/Integrate/IntegrateContext.swift b/Sources/XCRemoteCache/Commands/Prepare/Integrate/IntegrateContext.swift index c8750a56..aec0450c 100644 --- a/Sources/XCRemoteCache/Commands/Prepare/Integrate/IntegrateContext.swift +++ b/Sources/XCRemoteCache/Commands/Prepare/Integrate/IntegrateContext.swift @@ -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"), diff --git a/Sources/XCRemoteCache/Commands/Prepare/Integrate/XCRCBinariesPaths.swift b/Sources/XCRemoteCache/Commands/Prepare/Integrate/XCRCBinariesPaths.swift index 5325e7e2..73ec8344 100644 --- a/Sources/XCRemoteCache/Commands/Prepare/Integrate/XCRCBinariesPaths.swift +++ b/Sources/XCRemoteCache/Commands/Prepare/Integrate/XCRCBinariesPaths.swift @@ -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 diff --git a/Sources/xclipo/XCLipoMain.swift b/Sources/xclipo/XCLipoMain.swift new file mode 100644 index 00000000..8124d4a6 --- /dev/null +++ b/Sources/xclipo/XCLipoMain.swift @@ -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)") + } + } +} diff --git a/Sources/xclipo/main.swift b/Sources/xclipo/main.swift new file mode 100644 index 00000000..38508ad4 --- /dev/null +++ b/Sources/xclipo/main.swift @@ -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() diff --git a/Tests/XCRemoteCacheTests/Commands/Prepare/Integrate/XcodeProjBuildSettingsIntegrateAppenderTests.swift b/Tests/XCRemoteCacheTests/Commands/Prepare/Integrate/XcodeProjBuildSettingsIntegrateAppenderTests.swift index cadf2244..34b6b00e 100644 --- a/Tests/XCRemoteCacheTests/Commands/Prepare/Integrate/XcodeProjBuildSettingsIntegrateAppenderTests.swift +++ b/Tests/XCRemoteCacheTests/Commands/Prepare/Integrate/XcodeProjBuildSettingsIntegrateAppenderTests.swift @@ -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"), diff --git a/cocoapods-plugin/lib/cocoapods-xcremotecache/command/hooks.rb b/cocoapods-plugin/lib/cocoapods-xcremotecache/command/hooks.rb index 1cdefce8..9d4dd30f 100644 --- a/cocoapods-plugin/lib/cocoapods-xcremotecache/command/hooks.rb +++ b/cocoapods-plugin/lib/cocoapods-xcremotecache/command/hooks.rb @@ -3,9 +3,9 @@ # Licensed 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. @@ -29,15 +29,15 @@ module Hooks FAT_ARCHIVE_NAME_INFIX = 'arm64-x86_64' XCRC_COOCAPODS_ROOT_KEY = 'XCRC_COOCAPODS_ROOT' - # List of plugins' user properties that should not be copied to .rcinfo + # List of plugins' user properties that should not be copied to .rcinfo CUSTOM_CONFIGURATION_KEYS = [ - 'enabled', + 'enabled', 'xcrc_location', 'exclude_targets', 'exclude_build_configurations', 'final_target', - 'check_build_configuration', - 'check_platform', + 'check_build_configuration', + 'check_platform', 'modify_lldb_init', 'fake_src_root', ] @@ -45,19 +45,19 @@ module Hooks class XCRemoteCache @@configuration = nil - def self.configure(c) + def self.configure(c) @@configuration = c end def self.set_configuration_default_values default_values = { 'mode' => 'consumer', - 'enabled' => true, + 'enabled' => true, 'xcrc_location' => "XCRC", 'exclude_build_configurations' => [], 'check_build_configuration' => 'Debug', - 'check_platform' => 'iphonesimulator', - 'modify_lldb_init' => true, + 'check_platform' => 'iphonesimulator', + 'modify_lldb_init' => true, 'xccc_file' => "#{BIN_DIR}/xccc", 'remote_commit_file' => "#{BIN_DIR}/arc.rc", 'exclude_targets' => [], @@ -75,12 +75,12 @@ def self.set_configuration_default_values def self.validate_configuration() required_values = [ - 'cache_addresses', + 'cache_addresses', 'primary_repo', 'check_build_configuration', 'check_platform' ] - + missing_configuration_values = required_values.select { |v| !@@configuration.key?(v) } unless missing_configuration_values.empty? throw "XCRemoteCache not fully configured. Make sure all required fields are provided. Missing fields are: #{missing_configuration_values.join(', ')}." @@ -105,7 +105,7 @@ def self.parent_dir(path, parent_count) end # @param target [Target] target to apply XCRemoteCache - # @param repo_distance [Integer] distance from the git repo root to the target's $SRCROOT + # @param repo_distance [Integer] distance from the git repo root to the target's $SRCROOT # @param xc_location [String] path to the dir with all XCRemoteCache binaries, relative to the repo root # @param xc_cc_path [String] path to the XCRemoteCache clang wrapper, relative to the repo root # @param mode [String] mode name ('consumer', 'producer', 'producer-fast' etc.) @@ -128,6 +128,7 @@ def self.enable_xcremotecache(target, repo_distance, xc_location, xc_cc_path, mo config.build_settings['LIBTOOL'] = ["$SRCROOT/#{srcroot_relative_xc_location}/xclibtool"] config.build_settings['LD'] = ["$SRCROOT/#{srcroot_relative_xc_location}/xcld"] config.build_settings['LDPLUSPLUS'] = ["$SRCROOT/#{srcroot_relative_xc_location}/xcldplusplus"] + config.build_settings['LIPO'] = ["$SRCROOT/#{srcroot_relative_xc_location}/xclipo"] config.build_settings['SWIFT_USE_INTEGRATED_DRIVER'] = ['NO'] config.build_settings['XCREMOTE_CACHE_FAKE_SRCROOT'] = fake_src_root @@ -156,7 +157,7 @@ def self.enable_xcremotecache(target, repo_distance, xc_location, xc_cc_path, mo prebuild_script.dependency_file = "$(TARGET_TEMP_DIR)/prebuild.d" # Move prebuild (last element) to the position before compile sources phase (to make it real 'prebuild') - if !existing_prebuild_script + if !existing_prebuild_script compile_phase_index = target.build_phases.index(target.source_build_phase) target.build_phases.insert(compile_phase_index, target.build_phases.delete(prebuild_script)) end @@ -184,7 +185,7 @@ def self.enable_xcremotecache(target, repo_distance, xc_location, xc_cc_path, mo ] postbuild_script.dependency_file = "$(TARGET_TEMP_DIR)/postbuild.d" # Move postbuild (last element) to the position after compile sources phase (to make it real 'postbuild') - if !existing_postbuild_script + if !existing_postbuild_script compile_phase_index = target.build_phases.index(target.source_build_phase) target.build_phases.insert(compile_phase_index + 1, target.build_phases.delete(postbuild_script)) end @@ -214,6 +215,7 @@ def self.disable_xcremotecache_for_target(target) config.build_settings.delete('CC') if config.build_settings.key?('CC') config.build_settings.delete('SWIFT_EXEC') if config.build_settings.key?('SWIFT_EXEC') config.build_settings.delete('LIBTOOL') if config.build_settings.key?('LIBTOOL') + config.build_settings.delete('LIPO') if config.build_settings.key?('LIPO') config.build_settings.delete('LD') if config.build_settings.key?('LD') config.build_settings.delete('LDPLUSPLUS') if config.build_settings.key?('LDPLUSPLUS') config.build_settings.delete('SWIFT_USE_INTEGRATED_DRIVER') if config.build_settings.key?('SWIFT_USE_INTEGRATED_DRIVER') @@ -226,9 +228,9 @@ def self.disable_xcremotecache_for_target(target) end # User project is not generated from scratch (contrary to `Pods`), delete all previous XCRemoteCache phases - target.build_phases.delete_if {|phase| + target.build_phases.delete_if {|phase| # Some phases (e.g. PBXSourcesBuildPhase) don't have strict name check respond_to? - if phase.respond_to?(:name) + if phase.respond_to?(:name) phase.name != nil && phase.name.start_with?("[XCRC]") end } @@ -240,9 +242,9 @@ def self.save_rcinfo(info, directory) end def self.download_xcrc_if_needed(local_location) - required_binaries = ['xcld', 'xcldplusplus', 'xclibtool', 'xcpostbuild', 'xcprebuild', 'xcprepare', 'xcswiftc'] + required_binaries = ['xcld', 'xcldplusplus', 'xclibtool', 'xclipo', 'xcpostbuild', 'xcprebuild', 'xcprepare', 'xcswiftc'] binaries_exist = required_binaries.reduce(true) do |exists, filename| - file_path = File.join(local_location, filename) + file_path = File.join(local_location, filename) exists = exists && File.exist?(file_path) end @@ -256,13 +258,13 @@ def self.download_xcrc_if_needed(local_location) if !system("unzip #{local_package_location} -d #{local_location}") throw "Unzipping XCRemoteCache failed" - end + end end def self.download_latest_xcrc_release(local_package_location) release_url = 'https://api.github.com/repos/spotify/XCRemoteCache/releases/latest' asset_url = nil - + URI.open(release_url) do |f| assets_array = JSON.parse(f.read)['assets'] # Pick fat archive @@ -270,7 +272,7 @@ def self.download_latest_xcrc_release(local_package_location) asset_url = asset_array['url'] end - if asset_url.nil? + if asset_url.nil? throw "Downloading XCRemoteCache failed" end @@ -283,7 +285,7 @@ def self.download_latest_xcrc_release(local_package_location) def self.add_cflags!(options, key, value) return if options.fetch('OTHER_CFLAGS',[]).include?(value) - options['OTHER_CFLAGS'] = remove_cflags!(options, key) << "#{key}=#{value}" + options['OTHER_CFLAGS'] = remove_cflags!(options, key) << "#{key}=#{value}" end def self.remove_cflags!(options, key) @@ -295,7 +297,7 @@ def self.remove_cflags!(options, key) def self.add_swiftflags!(options, key, value) return if options.fetch('OTHER_SWIFT_FLAGS','').include?(value) - options['OTHER_SWIFT_FLAGS'] = remove_swiftflags!(options, key) + " #{key} #{value}" + options['OTHER_SWIFT_FLAGS'] = remove_swiftflags!(options, key) + " #{key} #{value}" end def self.remove_swiftflags!(options, key) @@ -336,7 +338,7 @@ def self.clean_lldbinit_content(lldbinit_path) File.open(lldbinit_path) { |file| while(line = file.gets) != nil line = line.strip - if line == LLDB_INIT_COMMENT + if line == LLDB_INIT_COMMENT # skip current and next lines file.gets next @@ -351,7 +353,7 @@ def self.clean_lldbinit_content(lldbinit_path) def self.add_lldbinit_rewrite(lines_content, user_proj_directory,fake_src_root) all_lines = lines_content.clone all_lines << LLDB_INIT_COMMENT - all_lines << "settings set target.source-map #{fake_src_root} #{user_proj_directory}" + all_lines << "settings set target.source-map #{fake_src_root} #{user_proj_directory}" all_lines << "" all_lines end @@ -413,8 +415,8 @@ def self.save_lldbinit_rewrite(user_proj_directory,fake_src_root) # Remote cache is still disabled - no need to force Pods projects/targets regeneration next end - - # Force rebuilding all Pods project, because XCRC build steps and settings need to be added to Pods project/targets + + # Force rebuilding all Pods project, because XCRC build steps and settings need to be added to Pods project/targets # It is relevant only when 'incremental_installation' is enabled, otherwise installed_cache_path does not exist on a disk installed_cache_path = installer_context.sandbox.project_installation_cache_path if !was_previously_enabled && File.exist?(installed_cache_path) @@ -431,7 +433,7 @@ def self.save_lldbinit_rewrite(user_proj_directory,fake_src_root) user_project = installer_context.umbrella_targets[0].user_project - begin + begin user_proj_directory = File.dirname(user_project.path) set_configuration_default_values @@ -494,12 +496,12 @@ def self.save_lldbinit_rewrite(user_proj_directory,fake_src_root) end # Manual Pods/.rcinfo generation - + # all paths in .rcinfo are relative to the root so paths used in Pods.xcodeproj need to be aligned pods_path = Pathname.new(pods_proj_directory) root_path = Pathname.new(user_proj_directory) root_path_to_pods = root_path.relative_path_from(pods_path) - + pods_rcinfo = root_rcinfo.merge({ 'remote_commit_file' => "#{root_path_to_pods}/#{remote_commit_file}", 'xccc_file' => "#{root_path_to_pods}/#{xccc_location}" @@ -511,7 +513,7 @@ def self.save_lldbinit_rewrite(user_proj_directory,fake_src_root) # Enabled/disable XCRemoteCache for the main (user) project begin - # TODO: Do not compile xcc again. `xcprepare` compiles it in pre-install anyway + # TODO: Do not compile xcc again. `xcprepare` compiles it in pre-install anyway prepare_result = YAML.load`#{xcrc_location_absolute}/xcprepare --configuration #{check_build_configuration} --platform #{check_platform}` unless prepare_result['result'] || mode != 'consumer' # Uninstall the XCRemoteCache for the consumer mode diff --git a/cocoapods-plugin/lib/cocoapods-xcremotecache/gem_version.rb b/cocoapods-plugin/lib/cocoapods-xcremotecache/gem_version.rb index 9f3f113b..4c347c86 100644 --- a/cocoapods-plugin/lib/cocoapods-xcremotecache/gem_version.rb +++ b/cocoapods-plugin/lib/cocoapods-xcremotecache/gem_version.rb @@ -3,9 +3,9 @@ # Licensed 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. @@ -13,5 +13,5 @@ # limitations under the License. module CocoapodsXcremotecache - VERSION = "0.0.14" + VERSION = "0.0.15" end diff --git a/e2eTests/StandaloneSampleApp/StandaloneApp.xcodeproj/project.pbxproj b/e2eTests/StandaloneSampleApp/StandaloneApp.xcodeproj/project.pbxproj index 346464aa..327cb466 100644 --- a/e2eTests/StandaloneSampleApp/StandaloneApp.xcodeproj/project.pbxproj +++ b/e2eTests/StandaloneSampleApp/StandaloneApp.xcodeproj/project.pbxproj @@ -16,6 +16,8 @@ 36201A2A2843B3D3002FF70F /* MixedTarget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 36201A292843B3D3002FF70F /* MixedTarget.swift */; }; 36201A362843B435002FF70F /* libMixedTarget.a in Frameworks */ = {isa = PBXBuildFile; fileRef = 36201A272843B3D3002FF70F /* libMixedTarget.a */; }; 36201A392843BDDC002FF70F /* StandaloneObjc.m in Sources */ = {isa = PBXBuildFile; fileRef = 36201A382843BDDC002FF70F /* StandaloneObjc.m */; }; + 4E10D63029BBFD8000A8655C /* WatchExtensionExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E10D62F29BBFD8000A8655C /* WatchExtensionExtension.swift */; }; + 4E10D63229BBFD8000A8655C /* WatchExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4E10D63129BBFD8000A8655C /* WatchExtension.swift */; }; 4EE6CF4929B6C1A000AEE1B4 /* StaticFramework.h in Headers */ = {isa = PBXBuildFile; fileRef = 4EE6CF4829B6C1A000AEE1B4 /* StaticFramework.h */; settings = {ATTRIBUTES = (Public, ); }; }; 4EE6CF5329B6C1AF00AEE1B4 /* StaticFrameworkFile.swift in Sources */ = {isa = PBXBuildFile; fileRef = 4EE6CF5229B6C1AF00AEE1B4 /* StaticFrameworkFile.swift */; }; /* End PBXBuildFile section */ @@ -28,6 +30,13 @@ remoteGlobalIDString = 36201A262843B3D3002FF70F; remoteInfo = MixedTarget; }; + 4E10D63729BBFD8E00A8655C /* PBXContainerItemProxy */ = { + isa = PBXContainerItemProxy; + containerPortal = 36201A042843B3C3002FF70F /* Project object */; + proxyType = 1; + remoteGlobalIDString = 4EE6CF4529B6C1A000AEE1B4; + remoteInfo = StaticFramework; + }; /* End PBXContainerItemProxy section */ /* Begin PBXCopyFilesBuildPhase section */ @@ -67,9 +76,10 @@ 36201A302843B414002FF70F /* SomeObjC.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = SomeObjC.h; sourceTree = ""; }; 36201A372843BDDC002FF70F /* StandaloneObjc.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StandaloneObjc.h; sourceTree = ""; }; 36201A382843BDDC002FF70F /* StandaloneObjc.m */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.objc; path = StandaloneObjc.m; sourceTree = ""; }; - 4E628CA229B8066500AF2DB0 /* SandaloneWatchAppExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SandaloneWatchAppExtension.swift; sourceTree = ""; }; - 4E628CA429B8066500AF2DB0 /* SandaloneWatchApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = SandaloneWatchApp.swift; sourceTree = ""; }; - 4E628CA629B8066500AF2DB0 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; + 4E10D62D29BBFD8000A8655C /* WatchExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.extensionkit-extension"; includeInIndex = 0; path = WatchExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; + 4E10D62F29BBFD8000A8655C /* WatchExtensionExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchExtensionExtension.swift; sourceTree = ""; }; + 4E10D63129BBFD8000A8655C /* WatchExtension.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = WatchExtension.swift; sourceTree = ""; }; + 4E10D63329BBFD8000A8655C /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 4EE6CF4629B6C1A000AEE1B4 /* StaticFramework.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = StaticFramework.framework; sourceTree = BUILT_PRODUCTS_DIR; }; 4EE6CF4829B6C1A000AEE1B4 /* StaticFramework.h */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.c.h; path = StaticFramework.h; sourceTree = ""; }; 4EE6CF5229B6C1AF00AEE1B4 /* StaticFrameworkFile.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = StaticFrameworkFile.swift; sourceTree = ""; }; @@ -91,6 +101,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 4E10D62A29BBFD8000A8655C /* Frameworks */ = { + isa = PBXFrameworksBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 4EE6CF4329B6C1A000AEE1B4 /* Frameworks */ = { isa = PBXFrameworksBuildPhase; buildActionMask = 2147483647; @@ -107,7 +124,7 @@ 36201A0E2843B3C3002FF70F /* StandaloneApp */, 36201A282843B3D3002FF70F /* MixedTarget */, 4EE6CF4729B6C1A000AEE1B4 /* StaticFramework */, - 4E628CA129B8066500AF2DB0 /* SandaloneWatchApp */, + 4E10D62E29BBFD8000A8655C /* WatchExtension */, 36201A0D2843B3C3002FF70F /* Products */, 36201A352843B435002FF70F /* Frameworks */, ); @@ -119,6 +136,7 @@ 36201A0C2843B3C3002FF70F /* StandaloneApp.app */, 36201A272843B3D3002FF70F /* libMixedTarget.a */, 4EE6CF4629B6C1A000AEE1B4 /* StaticFramework.framework */, + 4E10D62D29BBFD8000A8655C /* WatchExtension.appex */, ); name = Products; sourceTree = ""; @@ -156,14 +174,14 @@ name = Frameworks; sourceTree = ""; }; - 4E628CA129B8066500AF2DB0 /* SandaloneWatchApp */ = { + 4E10D62E29BBFD8000A8655C /* WatchExtension */ = { isa = PBXGroup; children = ( - 4E628CA229B8066500AF2DB0 /* SandaloneWatchAppExtension.swift */, - 4E628CA429B8066500AF2DB0 /* SandaloneWatchApp.swift */, - 4E628CA629B8066500AF2DB0 /* Info.plist */, + 4E10D62F29BBFD8000A8655C /* WatchExtensionExtension.swift */, + 4E10D63129BBFD8000A8655C /* WatchExtension.swift */, + 4E10D63329BBFD8000A8655C /* Info.plist */, ); - path = SandaloneWatchApp; + path = WatchExtension; sourceTree = ""; }; 4EE6CF4729B6C1A000AEE1B4 /* StaticFramework */ = { @@ -226,6 +244,24 @@ productReference = 36201A272843B3D3002FF70F /* libMixedTarget.a */; productType = "com.apple.product-type.library.static"; }; + 4E10D62C29BBFD8000A8655C /* WatchExtension */ = { + isa = PBXNativeTarget; + buildConfigurationList = 4E10D63429BBFD8000A8655C /* Build configuration list for PBXNativeTarget "WatchExtension" */; + buildPhases = ( + 4E10D62929BBFD8000A8655C /* Sources */, + 4E10D62A29BBFD8000A8655C /* Frameworks */, + 4E10D62B29BBFD8000A8655C /* Resources */, + ); + buildRules = ( + ); + dependencies = ( + 4E10D63829BBFD8E00A8655C /* PBXTargetDependency */, + ); + name = WatchExtension; + productName = WatchExtension; + productReference = 4E10D62D29BBFD8000A8655C /* WatchExtension.appex */; + productType = "com.apple.product-type.extensionkit-extension"; + }; 4EE6CF4529B6C1A000AEE1B4 /* StaticFramework */ = { isa = PBXNativeTarget; buildConfigurationList = 4EE6CF5129B6C1A000AEE1B4 /* Build configuration list for PBXNativeTarget "StaticFramework" */; @@ -262,6 +298,9 @@ CreatedOnToolsVersion = 13.2.1; LastSwiftMigration = 1320; }; + 4E10D62C29BBFD8000A8655C = { + CreatedOnToolsVersion = 14.2; + }; 4EE6CF4529B6C1A000AEE1B4 = { CreatedOnToolsVersion = 14.2; LastSwiftMigration = 1420; @@ -284,6 +323,7 @@ 36201A0B2843B3C3002FF70F /* StandaloneApp */, 36201A262843B3D3002FF70F /* MixedTarget */, 4EE6CF4529B6C1A000AEE1B4 /* StaticFramework */, + 4E10D62C29BBFD8000A8655C /* WatchExtension */, ); }; /* End PBXProject section */ @@ -299,6 +339,13 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 4E10D62B29BBFD8000A8655C /* Resources */ = { + isa = PBXResourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + ); + runOnlyForDeploymentPostprocessing = 0; + }; 4EE6CF4429B6C1A000AEE1B4 /* Resources */ = { isa = PBXResourcesBuildPhase; buildActionMask = 2147483647; @@ -353,6 +400,15 @@ ); runOnlyForDeploymentPostprocessing = 0; }; + 4E10D62929BBFD8000A8655C /* Sources */ = { + isa = PBXSourcesBuildPhase; + buildActionMask = 2147483647; + files = ( + 4E10D63029BBFD8000A8655C /* WatchExtensionExtension.swift in Sources */, + 4E10D63229BBFD8000A8655C /* WatchExtension.swift in Sources */, + ); + runOnlyForDeploymentPostprocessing = 0; + }; 4EE6CF4229B6C1A000AEE1B4 /* Sources */ = { isa = PBXSourcesBuildPhase; buildActionMask = 2147483647; @@ -369,6 +425,11 @@ target = 36201A262843B3D3002FF70F /* MixedTarget */; targetProxy = 36201A332843B431002FF70F /* PBXContainerItemProxy */; }; + 4E10D63829BBFD8E00A8655C /* PBXTargetDependency */ = { + isa = PBXTargetDependency; + target = 4EE6CF4529B6C1A000AEE1B4 /* StaticFramework */; + targetProxy = 4E10D63729BBFD8E00A8655C /* PBXContainerItemProxy */; + }; /* End PBXTargetDependency section */ /* Begin PBXVariantGroup section */ @@ -607,6 +668,62 @@ }; name = Release; }; + 4E10D63529BBFD8000A8655C /* Debug */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = WatchExtension/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = WatchExtension; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + "@executable_path/../../../../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.xcremotecache.WatchExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 4.0; + }; + name = Debug; + }; + 4E10D63629BBFD8000A8655C /* Release */ = { + isa = XCBuildConfiguration; + buildSettings = { + CLANG_CXX_LANGUAGE_STANDARD = "gnu++20"; + CODE_SIGN_STYLE = Automatic; + CURRENT_PROJECT_VERSION = 1; + GENERATE_INFOPLIST_FILE = YES; + INFOPLIST_FILE = WatchExtension/Info.plist; + INFOPLIST_KEY_CFBundleDisplayName = WatchExtension; + INFOPLIST_KEY_NSHumanReadableCopyright = ""; + LD_RUNPATH_SEARCH_PATHS = ( + "$(inherited)", + "@executable_path/Frameworks", + "@executable_path/../../Frameworks", + "@executable_path/../../../../Frameworks", + ); + MARKETING_VERSION = 1.0; + PRODUCT_BUNDLE_IDENTIFIER = com.xcremotecache.WatchExtension; + PRODUCT_NAME = "$(TARGET_NAME)"; + SDKROOT = watchos; + SKIP_INSTALL = YES; + SWIFT_EMIT_LOC_STRINGS = YES; + SWIFT_VERSION = 5.0; + TARGETED_DEVICE_FAMILY = 4; + WATCHOS_DEPLOYMENT_TARGET = 4.0; + }; + name = Release; + }; 4EE6CF4F29B6C1A000AEE1B4 /* Debug */ = { isa = XCBuildConfiguration; buildSettings = { @@ -710,6 +827,15 @@ defaultConfigurationIsVisible = 0; defaultConfigurationName = Release; }; + 4E10D63429BBFD8000A8655C /* Build configuration list for PBXNativeTarget "WatchExtension" */ = { + isa = XCConfigurationList; + buildConfigurations = ( + 4E10D63529BBFD8000A8655C /* Debug */, + 4E10D63629BBFD8000A8655C /* Release */, + ); + defaultConfigurationIsVisible = 0; + defaultConfigurationName = Release; + }; 4EE6CF5129B6C1A000AEE1B4 /* Build configuration list for PBXNativeTarget "StaticFramework" */ = { isa = XCConfigurationList; buildConfigurations = ( diff --git a/e2eTests/StandaloneSampleApp/StandaloneApp.xcodeproj/xcshareddata/xcschemes/SampleWatchApp.xcscheme b/e2eTests/StandaloneSampleApp/StandaloneApp.xcodeproj/xcshareddata/xcschemes/SampleWatchApp.xcscheme new file mode 100644 index 00000000..1e31ed14 --- /dev/null +++ b/e2eTests/StandaloneSampleApp/StandaloneApp.xcodeproj/xcshareddata/xcschemes/SampleWatchApp.xcscheme @@ -0,0 +1,92 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/e2eTests/StandaloneSampleApp/WatchExtension/Info.plist b/e2eTests/StandaloneSampleApp/WatchExtension/Info.plist new file mode 100644 index 00000000..8d15acbe --- /dev/null +++ b/e2eTests/StandaloneSampleApp/WatchExtension/Info.plist @@ -0,0 +1,11 @@ + + + + + EXAppExtensionAttributes + + EXExtensionPointIdentifier + com.apple.appintents-extension + + + diff --git a/e2eTests/StandaloneSampleApp/WatchExtension/WatchExtension.swift b/e2eTests/StandaloneSampleApp/WatchExtension/WatchExtension.swift new file mode 100644 index 00000000..239f0322 --- /dev/null +++ b/e2eTests/StandaloneSampleApp/WatchExtension/WatchExtension.swift @@ -0,0 +1,2 @@ +import AppIntents + diff --git a/e2eTests/StandaloneSampleApp/WatchExtension/WatchExtensionExtension.swift b/e2eTests/StandaloneSampleApp/WatchExtension/WatchExtensionExtension.swift new file mode 100644 index 00000000..655f8a9e --- /dev/null +++ b/e2eTests/StandaloneSampleApp/WatchExtension/WatchExtensionExtension.swift @@ -0,0 +1,4 @@ +import AppIntents + +struct WatchExtensionExtension: AppIntentsExtension { +} diff --git a/tasks/e2e.rb b/tasks/e2e.rb index 7bcde1aa..acafb699 100644 --- a/tasks/e2e.rb +++ b/tasks/e2e.rb @@ -58,7 +58,7 @@ system("pwd") system("#{XCRC_BINARIES}/xcprepare integrate --input StandaloneApp.xcodeproj --mode producer --final-producer-target StandaloneApp") # Build the project to fill in the cache - build_project(nil, "StandaloneApp.xcodeproj", 'StaticFramework', 'watch', 'watchOS') + build_project(nil, "StandaloneApp.xcodeproj", 'WatchExtension', 'watch', 'watchOS') build_project(nil, "StandaloneApp.xcodeproj", 'StandaloneApp') system("#{XCRC_BINARIES}/xcprepare stats --reset --format json") end @@ -74,14 +74,14 @@ prepare_for_standalone(consumer_srcroot) Dir.chdir(consumer_srcroot) do system("#{XCRC_BINARIES}/xcprepare integrate --input StandaloneApp.xcodeproj --mode consumer") - build_project(nil, "StandaloneApp.xcodeproj", 'StaticFramework', 'watch', 'watchOS', {'derivedDataPath' => "#{DERIVED_DATA_PATH}_consumer"}) + build_project(nil, "StandaloneApp.xcodeproj", 'WatchExtension', 'watch', 'watchOS', {'derivedDataPath' => "#{DERIVED_DATA_PATH}_consumer"}) build_project(nil, "StandaloneApp.xcodeproj", 'StandaloneApp', 'iphone', 'iOS', {'derivedDataPath' => "#{DERIVED_DATA_PATH}_consumer"}) valide_hit_rate puts 'Building standalone consumer with local change...' # Extra: validate local compilation of the Standalone ObjC code system("echo '' >> StandaloneApp/StandaloneObjc.m") - build_project(nil, "StandaloneApp.xcodeproj", 'StaticFramework', 'watch', 'watchOS', {'derivedDataPath' => "#{DERIVED_DATA_PATH}_consumer_local"}) + build_project(nil, "StandaloneApp.xcodeproj", 'WatchExtension', 'watch', 'watchOS', {'derivedDataPath' => "#{DERIVED_DATA_PATH}_consumer_local"}) build_project(nil, "StandaloneApp.xcodeproj", 'StandaloneApp', 'iphone', 'iOS', {'derivedDataPath' => "#{DERIVED_DATA_PATH}_consumer_local"}) end