Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fix unstable scheme generation #790

Merged
merged 3 commits into from Dec 16, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Jump to
Jump to file
Failed to load files.
Diff view
Diff view
1 change: 1 addition & 0 deletions CHANGELOG.md
Expand Up @@ -41,6 +41,7 @@ Please, check out guidelines: https://keepachangelog.com/en/1.0.0/

- Ensure custom search path settings are included in generated projects https://github.com/tuist/tuist/pull/751 by @kwridan
- Remove duplicate HEADER_SEARCH_PATHS https://github.com/tuist/tuist/pull/787 by @kwridan
- Fix unstable scheme generation https://github.com/tuist/tuist/pull/790 by @marciniwanicki

## 0.19.0

Expand Down
4 changes: 2 additions & 2 deletions Sources/TuistGenerator/Generator/SchemesGenerator.swift
Expand Up @@ -572,7 +572,7 @@ final class SchemesGenerator: SchemesGenerating {
private func commandlineArgruments(_ arguments: [String: Bool]) -> [XCScheme.CommandLineArguments.CommandLineArgument] {
arguments.map { key, enabled in
XCScheme.CommandLineArguments.CommandLineArgument(name: key, enabled: enabled)
}
}.sorted { $0.name < $1.name }
Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Does order matter when generating schemes?

Copy link
Contributor

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Wondering, why there are differences in order when generating subsequent times, can we identify the root cause of the problem?

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Generally it does, but this change is not regressing as the order is currently indeterministic. Please check #791 for more details.

Copy link
Collaborator Author

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

@lakpa Arguments are stored as a dictionary in the manifest, that's why they're shuffled.

public struct Arguments: Equatable, Codable {
    public let environment: [String: String]
    public let launch: [String: Bool]

    public init(environment: [String: String] = [:],
                launch: [String: Bool] = [:]) {
        self.environment = environment
        self.launch = launch
    }
}

}

/// Returns the scheme environment variables
Expand All @@ -583,7 +583,7 @@ final class SchemesGenerator: SchemesGenerating {
private func environmentVariables(_ environments: [String: String]) -> [XCScheme.EnvironmentVariable] {
environments.map { key, value in
XCScheme.EnvironmentVariable(variable: key, value: value, enabled: true)
}
}.sorted { $0.variable < $1.variable }
}

private func defaultDebugBuildConfigurationName(in project: Project) -> String {
Expand Down
19 changes: 16 additions & 3 deletions Tests/TuistGeneratorTests/Generator/SchemesGeneratorTests.swift
Expand Up @@ -345,14 +345,16 @@ final class SchemesGeneratorTests: XCTestCase {
func test_schemeLaunchAction() throws {
// Given
let projectPath = AbsolutePath("/somepath/Workspace/Projects/Project")
let environment = ["env1": "1", "env2": "2", "env3": "3", "env4": "4"]
let launch = ["arg1": true, "arg2": true, "arg3": false, "arg4": true]

let buildAction = BuildAction.test(targets: [TargetReference(projectPath: projectPath, name: "App")])
let runAction = RunAction.test(configurationName: "Release",
executable: TargetReference(projectPath: projectPath, name: "App"),
arguments: Arguments(environment: ["a": "b"], launch: ["some": true]))
arguments: Arguments(environment: environment, launch: launch))
let scheme = Scheme.test(buildAction: buildAction, runAction: runAction)

let app = Target.test(name: "App", product: .app, environment: ["a": "b"])
let app = Target.test(name: "App", product: .app, environment: environment)

let project = Project.test(path: projectPath, targets: [app])
let graph = Graph.create(dependencies: [(project: project, target: app, dependencies: [])])
Expand All @@ -371,7 +373,18 @@ final class SchemesGeneratorTests: XCTestCase {
let buildableReference = try XCTUnwrap(result.runnable?.buildableReference)

XCTAssertEqual(result.buildConfiguration, "Release")
XCTAssertEqual(result.environmentVariables, [XCScheme.EnvironmentVariable(variable: "a", value: "b", enabled: true)])
XCTAssertEqual(result.commandlineArguments, XCScheme.CommandLineArguments(arguments: [
XCScheme.CommandLineArguments.CommandLineArgument(name: "arg1", enabled: true),
XCScheme.CommandLineArguments.CommandLineArgument(name: "arg2", enabled: true),
XCScheme.CommandLineArguments.CommandLineArgument(name: "arg3", enabled: false),
XCScheme.CommandLineArguments.CommandLineArgument(name: "arg4", enabled: true),
]))
XCTAssertEqual(result.environmentVariables, [
XCScheme.EnvironmentVariable(variable: "env1", value: "1", enabled: true),
XCScheme.EnvironmentVariable(variable: "env2", value: "2", enabled: true),
XCScheme.EnvironmentVariable(variable: "env3", value: "3", enabled: true),
XCScheme.EnvironmentVariable(variable: "env4", value: "4", enabled: true),
])
XCTAssertEqual(buildableReference.referencedContainer, "container:Projects/Project/Project.xcodeproj")
XCTAssertEqual(buildableReference.buildableName, "App.app")
XCTAssertEqual(buildableReference.blueprintName, "App")
Expand Down
Expand Up @@ -79,10 +79,11 @@ final class StableXcodeProjIntegrationTests: TuistUnitTestCase {
let dependencies = try createDependencies(relativeTo: projectPath)
let frameworkTargets = try frameworksNames.map { try createFrameworkTarget(name: $0, depenendencies: dependencies) }
let appTarget = createAppTarget(settings: targetSettings, dependencies: frameworksNames)
let schemes = try createSchemes(appTarget: appTarget, frameworkTargets: frameworkTargets)
let project = createProject(path: projectPath,
settings: projectSettings,
targets: [appTarget] + frameworkTargets,
schemes: [])
schemes: schemes)
let workspace = try createWorkspace(projects: ["App"])
let tuistConfig = createTuistConfig()

Expand Down Expand Up @@ -232,10 +233,61 @@ final class StableXcodeProjIntegrationTests: TuistUnitTestCase {
return libraries
}

private func createSchemes(appTarget: Target, frameworkTargets: [Target]) throws -> [Scheme] {
let targets = try ([appTarget] + frameworkTargets).map(targetReference(from:))
return (0 ..< 10).map {
let boolStub = $0 % 2 == 0
return Scheme(
name: "Scheme \($0)",
shared: boolStub,
buildAction: BuildAction(targets: targets,
preActions: createExecutionActions(),
postActions: createExecutionActions()),
testAction: TestAction(targets: targets.map { TestableTarget(target: $0) },
arguments: createArguments(),
configurationName: "Debug",
coverage: boolStub,
codeCoverageTargets: targets,
preActions: createExecutionActions(),
postActions: createExecutionActions()),
runAction: RunAction(configurationName: "Debug",
executable: nil,
arguments: createArguments()),
archiveAction: ArchiveAction(configurationName: "Debug",
revealArchiveInOrganizer: boolStub,
preActions: createExecutionActions(),
postActions: createExecutionActions()))
}
}

private func createArguments() -> Arguments {
let environment = ( 0..<10 ).reduce([String: String]()) { acc, value in
var acc = acc
acc["Environment\(value)"] = "EnvironmentValue\(value)"
return acc
}
let launch = ( 0..<10 ).reduce([String: Bool]()) { acc, value in
var acc = acc
acc["Launch\(value)"] = value % 2 == 0
return acc
}
return Arguments(environment: environment, launch: launch)
}

private func createExecutionActions() -> [ExecutionAction] {
( 0..<10 ).map {
ExecutionAction(title: "ExecutionAction\($0)", scriptText: "ScripText\($0)", target: nil)
}
}

private func pathTo(_ relativePath: String) throws -> AbsolutePath {
let temporaryPath = try self.temporaryPath()
return temporaryPath.appending(RelativePath(relativePath))
}

private func targetReference(from target: Target) throws -> TargetReference {
return TargetReference(projectPath: try pathTo("App"), name: target.name)
}
}

extension XCWorkspace {
Expand Down