• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1// Copyright 2019 The Chromium Authors. All rights reserved.
2// Use of this source code is governed by a BSD-style license that can be
3// found in the LICENSE file.
4
5import 'package:meta/meta.dart';
6
7import '../application_package.dart';
8import '../base/file_system.dart';
9import '../build_info.dart';
10import '../globals.dart';
11import '../ios/plist_parser.dart';
12import '../project.dart';
13
14/// Tests whether a [FileSystemEntity] is an macOS bundle directory
15bool _isBundleDirectory(FileSystemEntity entity) =>
16    entity is Directory && entity.path.endsWith('.app');
17
18abstract class MacOSApp extends ApplicationPackage {
19  MacOSApp({@required String projectBundleId}) : super(id: projectBundleId);
20
21  /// Creates a new [MacOSApp] from a macOS project directory.
22  factory MacOSApp.fromMacOSProject(MacOSProject project) {
23    return BuildableMacOSApp(project);
24  }
25
26  /// Creates a new [MacOSApp] from an existing app bundle.
27  ///
28  /// `applicationBinary` is the path to the framework directory created by an
29  /// Xcode build. By default, this is located under
30  /// "~/Library/Developer/Xcode/DerivedData/" and contains an executable
31  /// which is expected to start the application and send the observatory
32  /// port over stdout.
33  factory MacOSApp.fromPrebuiltApp(FileSystemEntity applicationBinary) {
34    final _ExecutableAndId executableAndId = _executableFromBundle(applicationBinary);
35    final Directory applicationBundle = fs.directory(applicationBinary);
36    return PrebuiltMacOSApp(
37      bundleDir: applicationBundle,
38      bundleName: applicationBundle.path,
39      projectBundleId: executableAndId.id,
40      executable: executableAndId.executable,
41    );
42  }
43
44  /// Look up the executable name for a macOS application bundle.
45  static _ExecutableAndId _executableFromBundle(Directory applicationBundle) {
46    final FileSystemEntityType entityType = fs.typeSync(applicationBundle.path);
47    if (entityType == FileSystemEntityType.notFound) {
48      printError('File "${applicationBundle.path}" does not exist.');
49      return null;
50    }
51    Directory bundleDir;
52    if (entityType == FileSystemEntityType.directory) {
53      final Directory directory = fs.directory(applicationBundle);
54      if (!_isBundleDirectory(directory)) {
55        printError('Folder "${applicationBundle.path}" is not an app bundle.');
56        return null;
57      }
58      bundleDir = fs.directory(applicationBundle);
59    } else {
60      printError('Folder "${applicationBundle.path}" is not an app bundle.');
61      return null;
62    }
63    final String plistPath = fs.path.join(bundleDir.path, 'Contents', 'Info.plist');
64    if (!fs.file(plistPath).existsSync()) {
65      printError('Invalid prebuilt macOS app. Does not contain Info.plist.');
66      return null;
67    }
68    final Map<String, dynamic> propertyValues = PlistParser.instance.parseFile(plistPath);
69    final String id = propertyValues[PlistParser.kCFBundleIdentifierKey];
70    final String executableName = propertyValues[PlistParser.kCFBundleExecutable];
71    if (id == null) {
72      printError('Invalid prebuilt macOS app. Info.plist does not contain bundle identifier');
73      return null;
74    }
75    final String executable = fs.path.join(bundleDir.path, 'Contents', 'MacOS', executableName);
76    if (!fs.file(executable).existsSync()) {
77      printError('Could not find macOS binary at $executable');
78    }
79    return _ExecutableAndId(executable, id);
80  }
81
82  @override
83  String get displayName => id;
84
85  String applicationBundle(BuildMode buildMode);
86
87  String executable(BuildMode buildMode);
88}
89
90class PrebuiltMacOSApp extends MacOSApp {
91  PrebuiltMacOSApp({
92    @required this.bundleDir,
93    @required this.bundleName,
94    @required this.projectBundleId,
95    @required String executable,
96  }) : _executable = executable,
97       super(projectBundleId: projectBundleId);
98
99  final Directory bundleDir;
100  final String bundleName;
101  final String projectBundleId;
102
103  final String _executable;
104
105  @override
106  String get name => bundleName;
107
108  @override
109  String applicationBundle(BuildMode buildMode) => bundleDir.path;
110
111  @override
112  String executable(BuildMode buildMode) => _executable;
113}
114
115class BuildableMacOSApp extends MacOSApp {
116  BuildableMacOSApp(this.project);
117
118  final MacOSProject project;
119
120  @override
121  String get name => 'macOS';
122
123  @override
124  String applicationBundle(BuildMode buildMode) {
125    final File appBundleNameFile = project.nameFile;
126    if (!appBundleNameFile.existsSync()) {
127      printError('Unable to find app name. ${appBundleNameFile.path} does not exist');
128      return null;
129    }
130    return fs.path.join(
131        getMacOSBuildDirectory(),
132        'Build',
133        'Products',
134        buildMode == BuildMode.debug ? 'Debug' : 'Release',
135        appBundleNameFile.readAsStringSync().trim());
136  }
137
138  @override
139  String executable(BuildMode buildMode) {
140    final String directory = applicationBundle(buildMode);
141    if (directory == null) {
142      return null;
143    }
144    final _ExecutableAndId executableAndId = MacOSApp._executableFromBundle(fs.directory(directory));
145    return executableAndId?.executable;
146  }
147}
148
149class _ExecutableAndId {
150  _ExecutableAndId(this.executable, this.id);
151
152  final String executable;
153  final String id;
154}
155