application_package.dart 5.28 KB
Newer Older
Ian Hickson's avatar
Ian Hickson committed
1
// Copyright 2014 The Flutter Authors. All rights reserved.
2 3 4 5 6 7 8
// Use of this source code is governed by a BSD-style license that can be
// found in the LICENSE file.

import 'package:meta/meta.dart';

import '../application_package.dart';
import '../base/file_system.dart';
9
import '../base/utils.dart';
10
import '../build_info.dart';
11
import '../globals.dart' as globals;
12
import '../ios/plist_parser.dart';
13
import '../project.dart';
14

15
/// Tests whether a [FileSystemEntity] is an macOS bundle directory
16 17 18 19 20 21
bool _isBundleDirectory(FileSystemEntity entity) =>
    entity is Directory && entity.path.endsWith('.app');

abstract class MacOSApp extends ApplicationPackage {
  MacOSApp({@required String projectBundleId}) : super(id: projectBundleId);

22 23 24 25 26
  /// Creates a new [MacOSApp] from a macOS project directory.
  factory MacOSApp.fromMacOSProject(MacOSProject project) {
    return BuildableMacOSApp(project);
  }

27 28 29 30 31 32 33
  /// Creates a new [MacOSApp] from an existing app bundle.
  ///
  /// `applicationBinary` is the path to the framework directory created by an
  /// Xcode build. By default, this is located under
  /// "~/Library/Developer/Xcode/DerivedData/" and contains an executable
  /// which is expected to start the application and send the observatory
  /// port over stdout.
34
  factory MacOSApp.fromPrebuiltApp(FileSystemEntity applicationBinary) {
35
    final _ExecutableAndId executableAndId = _executableFromBundle(applicationBinary);
36
    final Directory applicationBundle = globals.fs.directory(applicationBinary);
37 38 39
    return PrebuiltMacOSApp(
      bundleDir: applicationBundle,
      bundleName: applicationBundle.path,
40 41
      projectBundleId: executableAndId.id,
      executable: executableAndId.executable,
42 43 44 45
    );
  }

  /// Look up the executable name for a macOS application bundle.
46
  static _ExecutableAndId _executableFromBundle(FileSystemEntity applicationBundle) {
47
    final FileSystemEntityType entityType = globals.fs.typeSync(applicationBundle.path);
48
    if (entityType == FileSystemEntityType.notFound) {
49
      globals.printError('File "${applicationBundle.path}" does not exist.');
50 51 52 53
      return null;
    }
    Directory bundleDir;
    if (entityType == FileSystemEntityType.directory) {
54
      final Directory directory = globals.fs.directory(applicationBundle);
55
      if (!_isBundleDirectory(directory)) {
56
        globals.printError('Folder "${applicationBundle.path}" is not an app bundle.');
57 58
        return null;
      }
59
      bundleDir = globals.fs.directory(applicationBundle);
60
    } else {
61
      globals.printError('Folder "${applicationBundle.path}" is not an app bundle.');
62 63
      return null;
    }
64 65 66
    final String plistPath = globals.fs.path.join(bundleDir.path, 'Contents', 'Info.plist');
    if (!globals.fs.file(plistPath).existsSync()) {
      globals.printError('Invalid prebuilt macOS app. Does not contain Info.plist.');
67 68
      return null;
    }
69
    final Map<String, dynamic> propertyValues = globals.plistParser.parseFile(plistPath);
70 71
    final String id = propertyValues[PlistParser.kCFBundleIdentifierKey] as String;
    final String executableName = propertyValues[PlistParser.kCFBundleExecutable] as String;
72
    if (id == null) {
73
      globals.printError('Invalid prebuilt macOS app. Info.plist does not contain bundle identifier');
74 75
      return null;
    }
76 77 78
    final String executable = globals.fs.path.join(bundleDir.path, 'Contents', 'MacOS', executableName);
    if (!globals.fs.file(executable).existsSync()) {
      globals.printError('Could not find macOS binary at $executable');
79
    }
80
    return _ExecutableAndId(executable, id);
81 82 83 84
  }

  @override
  String get displayName => id;
85 86 87 88

  String applicationBundle(BuildMode buildMode);

  String executable(BuildMode buildMode);
89 90
}

91
class PrebuiltMacOSApp extends MacOSApp {
92 93 94
  PrebuiltMacOSApp({
    @required this.bundleDir,
    @required this.bundleName,
95 96 97 98
    @required this.projectBundleId,
    @required String executable,
  }) : _executable = executable,
       super(projectBundleId: projectBundleId);
99 100 101

  final Directory bundleDir;
  final String bundleName;
102 103 104
  final String projectBundleId;

  final String _executable;
105 106 107

  @override
  String get name => bundleName;
108

109 110 111 112 113
  @override
  String applicationBundle(BuildMode buildMode) => bundleDir.path;

  @override
  String executable(BuildMode buildMode) => _executable;
114 115 116 117 118 119 120 121 122
}

class BuildableMacOSApp extends MacOSApp {
  BuildableMacOSApp(this.project);

  final MacOSProject project;

  @override
  String get name => 'macOS';
123 124 125 126 127

  @override
  String applicationBundle(BuildMode buildMode) {
    final File appBundleNameFile = project.nameFile;
    if (!appBundleNameFile.existsSync()) {
128
      globals.printError('Unable to find app name. ${appBundleNameFile.path} does not exist');
129 130
      return null;
    }
131
    return globals.fs.path.join(
132 133 134
        getMacOSBuildDirectory(),
        'Build',
        'Products',
135
        toTitleCase(getNameForBuildMode(buildMode)),
136 137 138 139 140 141 142 143 144
        appBundleNameFile.readAsStringSync().trim());
  }

  @override
  String executable(BuildMode buildMode) {
    final String directory = applicationBundle(buildMode);
    if (directory == null) {
      return null;
    }
145
    final _ExecutableAndId executableAndId = MacOSApp._executableFromBundle(globals.fs.directory(directory));
146
    return executableAndId?.executable;
147
  }
148 149
}

150 151
class _ExecutableAndId {
  _ExecutableAndId(this.executable, this.id);
152 153 154

  final String executable;
  final String id;
155
}