// Copyright 2014 The Bazel Authors. All rights reserved. // // 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. // See the License for the specific language governing permissions and // limitations under the License. package com.google.devtools.build.xcode.xcodegen; import com.google.common.base.Function; import com.google.common.collect.ImmutableList; import com.google.common.collect.Iterables; import com.google.devtools.build.xcode.xcodegen.proto.XcodeGenProtos.Control; import com.google.devtools.build.xcode.xcodegen.proto.XcodeGenProtos.TargetControl; import com.google.devtools.common.options.Option; import com.google.devtools.common.options.Options; import com.google.devtools.common.options.OptionsBase; import com.google.devtools.common.options.OptionsParser; import com.google.devtools.common.options.OptionsParsingException; import com.facebook.buck.apple.xcode.xcodeproj.PBXProject; import java.io.IOException; import java.io.InputStream; import java.io.OutputStream; import java.nio.file.FileSystem; import java.nio.file.FileSystems; import java.nio.file.Files; import java.nio.file.Path; import java.util.Iterator; import java.util.List; /** * Entry-point for the command-line Xcode project generator. */ public class XcodeGen { /** * Options for {@link XcodeGen}. */ public static class XcodeGenOptions extends OptionsBase { @Option( name = "control", help = "Path to a control file, which contains only a binary serialized instance of " + "the Control protocol buffer. Required.", defaultValue = "null") public String control; } public static void main(String[] args) throws IOException, OptionsParsingException { OptionsParser parser = OptionsParser.newOptionsParser(XcodeGenOptions.class); parser.parse(args); XcodeGenOptions options = parser.getOptions(XcodeGenOptions.class); if (options.control == null) { throw new IllegalArgumentException("--control must be specified\n" + Options.getUsage(XcodeGenOptions.class)); } FileSystem fileSystem = FileSystems.getDefault(); Control controlPb; try (InputStream in = Files.newInputStream(fileSystem.getPath(options.control))) { controlPb = Control.parseFrom(in); } Path pbxprojPath = fileSystem.getPath(controlPb.getPbxproj()); Iterator<String> srcList = allSourceFilePaths(controlPb).iterator(); Path workspaceRoot; // TODO(bazel-team): Remove this if-else clause once Bazel passes in the workspace root. if (controlPb.hasWorkspaceRoot()) { workspaceRoot = fileSystem.getPath(controlPb.getWorkspaceRoot()); } else if (!srcList.hasNext()) { workspaceRoot = XcodeprojGeneration.relativeWorkspaceRoot(pbxprojPath); } else { // Get the absolute path to the workspace root. // TODO(bazel-team): Remove this hack, possibly by converting Xcodegen to be run with // "bazel run" and using RUNFILES to get the workspace root. For now, this is needed to work // around Xcode's handling of symlinks not playing nicely with how Bazel stores output // artifacts in /private/var/tmp. This means a relative path from .xcodeproj in bazel-out to // the workspace root in .xcodeproj will not work properly at certain times during // Xcode/xcodebuild execution. Walking up the path of a known source file prevents having // to reason about a file that might only be accessible through a symlink, like a tools jar. Path relSourceFilePath = fileSystem.getPath(srcList.next()); Path absSourceFilePath = relSourceFilePath.toAbsolutePath(); workspaceRoot = absSourceFilePath; for (int i = 0; i < relSourceFilePath.getNameCount(); i++) { workspaceRoot = workspaceRoot.getParent(); } } try (OutputStream out = Files.newOutputStream(pbxprojPath)) { // This workspace root here is relative to the PWD, so that the .xccurrentversion // files can actually be read. The other workspaceRoot is relative to the .xcodeproj // root or is absolute. Path relativeWorkspaceRoot = fileSystem.getPath("."); PBXProject project = XcodeprojGeneration.xcodeproj( workspaceRoot, controlPb, ImmutableList.of( new CurrentVersionSetter(relativeWorkspaceRoot), new PbxReferencesGrouper(fileSystem))); XcodeprojGeneration.write(out, project); } } private static Iterable<String> allSourceFilePaths(Control control) { return Iterables.concat( Iterables.transform(control.getTargetList(), new Function<TargetControl, List<String>>() { public List<String> apply(TargetControl tc) { return tc.getSourceFileList(); } })); } }